A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.

Compare changes

Choose any two refs to compare.

+22
.npmignore
···
··· 1 + # Demo and development files 2 + demo/ 3 + src/ 4 + index.html 5 + 6 + # Build configuration 7 + vite.config.ts 8 + vite.config.d.ts 9 + tsconfig.app.json 10 + tsconfig.node.json 11 + eslint.config.js 12 + tsconfig.lib.tsbuildinfo 13 + 14 + # Dependencies 15 + node_modules/ 16 + package-lock.json 17 + bun.lock 18 + 19 + CLAUDE.md 20 + 21 + # Output directory 22 + lib/
+42
.tangled/workflows/upload-demo-to-wisp.yml
···
··· 1 + when: 2 + - event: ['push'] 3 + branch: ['main'] 4 + - event: ['manual'] 5 + engine: 'nixery' 6 + clone: 7 + skip: false 8 + depth: 1 9 + submodules: false 10 + dependencies: 11 + nixpkgs: 12 + - nodejs 13 + - coreutils 14 + - curl 15 + github:NixOS/nixpkgs/nixpkgs-unstable: 16 + - bun 17 + 18 + environment: 19 + SITE_PATH: 'demo' 20 + SITE_NAME: 'atproto-ui' 21 + WISP_HANDLE: 'ana.pds.nkp.pet' 22 + 23 + steps: 24 + - name: build demo 25 + command: | 26 + export PATH="$HOME/.nix-profile/bin:$PATH" 27 + 28 + # regenerate lockfile, https://github.com/npm/cli/pull/8184 makes rolldown not install 29 + rm package-lock.json bun.lock 30 + bun install 31 + 32 + # run directly with bun because of shebang issues in nix 33 + BUILD_TARGET=demo bun node_modules/.bin/vite build 34 + - name: upload to wisp 35 + command: | 36 + curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 37 + chmod +x wisp-cli 38 + ./wisp-cli \ 39 + "$WISP_HANDLE" \ 40 + --path "$SITE_PATH" \ 41 + --site "$SITE_NAME" \ 42 + --password "$WISP_APP_PASSWORD"
+701
CLAUDE.md
···
··· 1 + # AtReact Hooks Deep Dive 2 + 3 + ## Overview 4 + The AtReact hooks system provides a robust, cache-optimized layer for fetching AT Protocol data. All hooks follow React best practices with proper cleanup, cancellation, and stable references. 5 + 6 + --- 7 + 8 + ## Core Architecture Principles 9 + 10 + ### 1. **Three-Tier Caching Strategy** 11 + All data flows through three cache layers: 12 + - **DidCache** - DID documents, handle mappings, PDS endpoints 13 + - **BlobCache** - Media/image blobs with reference counting 14 + - **RecordCache** - AT Protocol records with deduplication 15 + 16 + ### 2. **Concurrent Request Deduplication** 17 + When multiple components request the same data, only one network request is made. Uses reference counting to manage in-flight requests. 18 + 19 + ### 3. **Stable Reference Pattern** 20 + Caches use memoized snapshots to prevent unnecessary re-renders: 21 + ```typescript 22 + // Only creates new snapshot if data actually changed 23 + if (existing && existing.did === did && existing.handle === handle) { 24 + return toSnapshot(existing); // Reuse existing 25 + } 26 + ``` 27 + 28 + ### 4. **Three-Tier Fallback for Bluesky** 29 + For `app.bsky.*` collections: 30 + 1. Try Bluesky appview API (fastest, public) 31 + 2. Fall back to Slingshot (microcosm service) 32 + 3. Finally query PDS directly 33 + 34 + --- 35 + 36 + ## Hook Catalog 37 + 38 + ## 1. `useDidResolution` 39 + **Purpose:** Resolves handles to DIDs or fetches DID documents 40 + 41 + ### Key Features: 42 + - **Bidirectional:** Works with handles OR DIDs 43 + - **Smart Caching:** Only fetches if not in cache 44 + - **Dual Resolution Paths:** 45 + - Handle โ†’ DID: Uses Slingshot first, then appview 46 + - DID โ†’ Document: Fetches full DID document for handle extraction 47 + 48 + ### State Flow: 49 + ```typescript 50 + Input: "alice.bsky.social" or "did:plc:xxx" 51 + โ†“ 52 + Check didCache 53 + โ†“ 54 + If handle: ensureHandle(resolver, handle) โ†’ DID 55 + If DID: ensureDidDoc(resolver, did) โ†’ DID doc + handle from alsoKnownAs 56 + โ†“ 57 + Return: { did, handle, loading, error } 58 + ``` 59 + 60 + ### Critical Implementation Details: 61 + - **Normalizes input** to lowercase for handles 62 + - **Memoizes input** to prevent effect re-runs 63 + - **Stabilizes error references** - only updates if message changes 64 + - **Cleanup:** Cancellation token prevents stale updates 65 + 66 + --- 67 + 68 + ## 2. `usePdsEndpoint` 69 + **Purpose:** Discovers the PDS endpoint for a DID 70 + 71 + ### Key Features: 72 + - **Depends on DID resolution** (implicit dependency) 73 + - **Extracts from DID document** if already cached 74 + - **Lazy fetching** - only when endpoint not in cache 75 + 76 + ### State Flow: 77 + ```typescript 78 + Input: DID 79 + โ†“ 80 + Check didCache.getByDid(did).pdsEndpoint 81 + โ†“ 82 + If missing: ensurePdsEndpoint(resolver, did) 83 + โ”œโ”€ Tries to get from existing DID doc 84 + โ””โ”€ Falls back to resolver.pdsEndpointForDid() 85 + โ†“ 86 + Return: { endpoint, loading, error } 87 + ``` 88 + 89 + ### Service Discovery: 90 + Looks for `AtprotoPersonalDataServer` service in DID document: 91 + ```json 92 + { 93 + "service": [{ 94 + "type": "AtprotoPersonalDataServer", 95 + "serviceEndpoint": "https://pds.example.com" 96 + }] 97 + } 98 + ``` 99 + 100 + --- 101 + 102 + ## 3. `useAtProtoRecord` 103 + **Purpose:** Fetches a single AT Protocol record with smart routing 104 + 105 + ### Key Features: 106 + - **Collection-aware routing:** Bluesky vs other protocols 107 + - **RecordCache deduplication:** Multiple components = one fetch 108 + - **Cleanup with reference counting** 109 + 110 + ### State Flow: 111 + ```typescript 112 + Input: { did, collection, rkey } 113 + โ†“ 114 + If collection.startsWith("app.bsky."): 115 + โ””โ”€ useBlueskyAppview() โ†’ Three-tier fallback 116 + Else: 117 + โ”œโ”€ useDidResolution(did) 118 + โ”œโ”€ usePdsEndpoint(resolved.did) 119 + โ””โ”€ recordCache.ensure() โ†’ Fetch from PDS 120 + โ†“ 121 + Return: { record, loading, error } 122 + ``` 123 + 124 + ### RecordCache Deduplication: 125 + ```typescript 126 + // First component calling this 127 + const { promise, release } = recordCache.ensure(did, collection, rkey, loader) 128 + // refCount = 1 129 + 130 + // Second component calling same record 131 + const { promise, release } = recordCache.ensure(...) // Same promise! 132 + // refCount = 2 133 + 134 + // On cleanup, both call release() 135 + // Only aborts when refCount reaches 0 136 + ``` 137 + 138 + --- 139 + 140 + ## 4. `useBlueskyAppview` 141 + **Purpose:** Fetches Bluesky records with appview optimization 142 + 143 + ### Key Features: 144 + - **Collection-aware endpoints:** 145 + - `app.bsky.actor.profile` โ†’ `app.bsky.actor.getProfile` 146 + - `app.bsky.feed.post` โ†’ `app.bsky.feed.getPostThread` 147 + - **CDN URL extraction:** Parses CDN URLs to extract CIDs 148 + - **Atomic state updates:** Uses reducer for complex state 149 + 150 + ### Three-Tier Fallback with Source Tracking: 151 + ```typescript 152 + async function fetchWithFallback() { 153 + // Tier 1: Appview (if endpoint mapped) 154 + try { 155 + const result = await fetchFromAppview(did, collection, rkey); 156 + return { record: result, source: "appview" }; 157 + } catch {} 158 + 159 + // Tier 2: Slingshot 160 + try { 161 + const result = await fetchFromSlingshot(did, collection, rkey); 162 + return { record: result, source: "slingshot" }; 163 + } catch {} 164 + 165 + // Tier 3: PDS 166 + try { 167 + const result = await fetchFromPds(did, collection, rkey); 168 + return { record: result, source: "pds" }; 169 + } catch {} 170 + 171 + // All tiers failed - provide helpful error for banned Bluesky accounts 172 + if (pdsEndpoint.includes('.bsky.network')) { 173 + throw new Error('Record unavailable. The Bluesky PDS may be unreachable or the account may be banned.'); 174 + } 175 + 176 + throw new Error('Failed to fetch record from all sources'); 177 + } 178 + ``` 179 + 180 + The `source` field in the result accurately indicates which tier successfully fetched the data, enabling debugging and analytics. 181 + 182 + ### CDN URL Handling: 183 + Appview returns CDN URLs like: 184 + ``` 185 + https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg 186 + ``` 187 + 188 + Hook extracts CID (`bafkreixxx`) and creates standard Blob object: 189 + ```typescript 190 + { 191 + $type: "blob", 192 + ref: { $link: "bafkreixxx" }, 193 + mimeType: "image/jpeg", 194 + size: 0, 195 + cdnUrl: "https://cdn.bsky.app/..." // Preserved for fast rendering 196 + } 197 + ``` 198 + 199 + ### Reducer Pattern: 200 + ```typescript 201 + type Action = 202 + | { type: "SET_LOADING"; loading: boolean } 203 + | { type: "SET_SUCCESS"; record: T; source: "appview" | "slingshot" | "pds" } 204 + | { type: "SET_ERROR"; error: Error } 205 + | { type: "RESET" }; 206 + 207 + // Atomic state updates, no race conditions 208 + dispatch({ type: "SET_SUCCESS", record, source }); 209 + ``` 210 + 211 + --- 212 + 213 + ## 5. `useLatestRecord` 214 + **Purpose:** Fetches the most recent record from a collection 215 + 216 + ### Key Features: 217 + - **Timestamp validation:** Skips records before 2023 (pre-ATProto) 218 + - **PDS-only:** Slingshot doesn't support `listRecords` 219 + - **Smart fetching:** Gets 3 records to handle invalid timestamps 220 + 221 + ### State Flow: 222 + ```typescript 223 + Input: { did, collection } 224 + โ†“ 225 + useDidResolution(did) 226 + usePdsEndpoint(did) 227 + โ†“ 228 + callListRecords(endpoint, did, collection, limit: 3) 229 + โ†“ 230 + Filter: isValidTimestamp(record) โ†’ year >= 2023 231 + โ†“ 232 + Return first valid record: { record, rkey, loading, error, empty } 233 + ``` 234 + 235 + ### Timestamp Validation: 236 + ```typescript 237 + function isValidTimestamp(record: unknown): boolean { 238 + const timestamp = record.createdAt || record.indexedAt; 239 + if (!timestamp) return true; // No timestamp, assume valid 240 + 241 + const date = new Date(timestamp); 242 + return date.getFullYear() >= 2023; // ATProto created in 2023 243 + } 244 + ``` 245 + 246 + --- 247 + 248 + ## 6. `usePaginatedRecords` 249 + **Purpose:** Cursor-based pagination with prefetching 250 + 251 + ### Key Features: 252 + - **Dual fetching modes:** 253 + - Author feed (appview) - for Bluesky posts with filters 254 + - Direct PDS - for all other collections 255 + - **Smart prefetching:** Loads next page in background 256 + - **Invalid timestamp filtering:** Same as `useLatestRecord` 257 + - **Request sequencing:** Prevents race conditions with `requestSeq` 258 + 259 + ### State Management: 260 + ```typescript 261 + // Pages stored as array 262 + pages: [ 263 + { records: [...], cursor: "abc" }, // page 0 264 + { records: [...], cursor: "def" }, // page 1 265 + { records: [...], cursor: undefined } // page 2 (last) 266 + ] 267 + pageIndex: 1 // Currently viewing page 1 268 + ``` 269 + 270 + ### Prefetch Logic: 271 + ```typescript 272 + useEffect(() => { 273 + const cursor = pages[pageIndex]?.cursor; 274 + if (!cursor || pages[pageIndex + 1]) return; // No cursor or already loaded 275 + 276 + // Prefetch next page in background 277 + fetchPage(identity, cursor, pageIndex + 1, "prefetch"); 278 + }, [pageIndex, pages]); 279 + ``` 280 + 281 + ### Author Feed vs PDS: 282 + ```typescript 283 + if (preferAuthorFeed && collection === "app.bsky.feed.post") { 284 + // Use app.bsky.feed.getAuthorFeed 285 + const res = await callAppviewRpc("app.bsky.feed.getAuthorFeed", { 286 + actor: handle || did, 287 + filter: "posts_with_media", // Optional filter 288 + includePins: true 289 + }); 290 + } else { 291 + // Use com.atproto.repo.listRecords 292 + const res = await callListRecords(pdsEndpoint, did, collection, limit); 293 + } 294 + ``` 295 + 296 + ### Race Condition Prevention: 297 + ```typescript 298 + const requestSeq = useRef(0); 299 + 300 + // On identity change 301 + resetState(); 302 + requestSeq.current += 1; // Invalidate in-flight requests 303 + 304 + // In fetch callback 305 + const token = requestSeq.current; 306 + // ... do async work ... 307 + if (token !== requestSeq.current) return; // Stale request, abort 308 + ``` 309 + 310 + --- 311 + 312 + ## 7. `useBlob` 313 + **Purpose:** Fetches and caches media blobs with object URL management 314 + 315 + ### Key Features: 316 + - **Automatic cleanup:** Revokes object URLs on unmount 317 + - **BlobCache deduplication:** Same blob = one fetch 318 + - **Reference counting:** Safe concurrent access 319 + 320 + ### State Flow: 321 + ```typescript 322 + Input: { did, cid } 323 + โ†“ 324 + useDidResolution(did) 325 + usePdsEndpoint(did) 326 + โ†“ 327 + Check blobCache.get(did, cid) 328 + โ†“ 329 + If missing: blobCache.ensure() โ†’ Fetch from PDS 330 + โ”œโ”€ GET /xrpc/com.atproto.sync.getBlob?did={did}&cid={cid} 331 + โ””โ”€ Store in cache 332 + โ†“ 333 + Create object URL: URL.createObjectURL(blob) 334 + โ†“ 335 + Return: { url, loading, error } 336 + โ†“ 337 + Cleanup: URL.revokeObjectURL(url) 338 + ``` 339 + 340 + ### Object URL Management: 341 + ```typescript 342 + const objectUrlRef = useRef<string>(); 343 + 344 + // On successful fetch 345 + const nextUrl = URL.createObjectURL(blob); 346 + const prevUrl = objectUrlRef.current; 347 + objectUrlRef.current = nextUrl; 348 + if (prevUrl) URL.revokeObjectURL(prevUrl); // Clean up old URL 349 + 350 + // On unmount 351 + useEffect(() => () => { 352 + if (objectUrlRef.current) { 353 + URL.revokeObjectURL(objectUrlRef.current); 354 + } 355 + }, []); 356 + ``` 357 + 358 + --- 359 + 360 + ## 8. `useBlueskyProfile` 361 + **Purpose:** Wrapper around `useBlueskyAppview` for profile records 362 + 363 + ### Key Features: 364 + - **Simplified interface:** Just pass DID 365 + - **Type conversion:** Converts ProfileRecord to BlueskyProfileData 366 + - **CID extraction:** Extracts avatar/banner CIDs from blobs 367 + 368 + ### Implementation: 369 + ```typescript 370 + export function useBlueskyProfile(did: string | undefined) { 371 + const { record, loading, error } = useBlueskyAppview<ProfileRecord>({ 372 + did, 373 + collection: "app.bsky.actor.profile", 374 + rkey: "self", 375 + }); 376 + 377 + const data = record ? { 378 + did: did || "", 379 + handle: "", // Populated by caller 380 + displayName: record.displayName, 381 + description: record.description, 382 + avatar: extractCidFromBlob(record.avatar), 383 + banner: extractCidFromBlob(record.banner), 384 + createdAt: record.createdAt, 385 + } : undefined; 386 + 387 + return { data, loading, error }; 388 + } 389 + ``` 390 + 391 + --- 392 + 393 + ## 9. `useBacklinks` 394 + **Purpose:** Fetches backlinks from Microcosm Constellation API 395 + 396 + ### Key Features: 397 + - **Specialized use case:** Tangled stars, etc. 398 + - **Abort controller:** Cancels in-flight requests 399 + - **Refetch support:** Manual refresh capability 400 + 401 + ### State Flow: 402 + ```typescript 403 + Input: { subject: "at://did:plc:xxx/sh.tangled.repo/yyy", source: "sh.tangled.feed.star:subject" } 404 + โ†“ 405 + GET https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks 406 + ?subject={subject}&source={source}&limit={limit} 407 + โ†“ 408 + Return: { backlinks: [...], total, loading, error, refetch } 409 + ``` 410 + 411 + --- 412 + 413 + ## 10. `useRepoLanguages` 414 + **Purpose:** Fetches language statistics from Tangled knot server 415 + 416 + ### Key Features: 417 + - **Branch fallback:** Tries "main", then "master" 418 + - **Knot server query:** For repository analysis 419 + 420 + ### State Flow: 421 + ```typescript 422 + Input: { knot: "knot.gaze.systems", did, repoName, branch } 423 + โ†“ 424 + GET https://{knot}/xrpc/sh.tangled.repo.languages 425 + ?repo={did}/{repoName}&ref={branch} 426 + โ†“ 427 + If 404: Try fallback branch 428 + โ†“ 429 + Return: { data: { languages: {...} }, loading, error } 430 + ``` 431 + 432 + --- 433 + 434 + ## Cache Implementation Deep Dive 435 + 436 + ### DidCache 437 + **Purpose:** Cache DID documents, handle mappings, PDS endpoints 438 + 439 + ```typescript 440 + class DidCache { 441 + private byHandle = new Map<string, DidCacheEntry>(); 442 + private byDid = new Map<string, DidCacheEntry>(); 443 + private handlePromises = new Map<string, Promise<...>>(); 444 + private docPromises = new Map<string, Promise<...>>(); 445 + private pdsPromises = new Map<string, Promise<...>>(); 446 + 447 + // Memoized snapshots prevent re-renders 448 + private toSnapshot(entry): DidCacheSnapshot { 449 + if (entry.snapshot) return entry.snapshot; // Reuse 450 + entry.snapshot = { did, handle, doc, pdsEndpoint }; 451 + return entry.snapshot; 452 + } 453 + } 454 + ``` 455 + 456 + **Key methods:** 457 + - `getByHandle(handle)` - Instant cache lookup 458 + - `getByDid(did)` - Instant cache lookup 459 + - `ensureHandle(resolver, handle)` - Deduplicated resolution 460 + - `ensureDidDoc(resolver, did)` - Deduplicated doc fetch 461 + - `ensurePdsEndpoint(resolver, did)` - Deduplicated PDS discovery 462 + 463 + **Snapshot stability:** 464 + ```typescript 465 + memoize(entry) { 466 + const existing = this.byDid.get(did); 467 + 468 + // Data unchanged? Reuse snapshot (same reference) 469 + if (existing && existing.did === did && 470 + existing.handle === handle && ...) { 471 + return toSnapshot(existing); // Prevents re-render! 472 + } 473 + 474 + // Data changed, create new entry 475 + const merged = { did, handle, doc, pdsEndpoint, snapshot: undefined }; 476 + this.byDid.set(did, merged); 477 + return toSnapshot(merged); 478 + } 479 + ``` 480 + 481 + ### BlobCache 482 + **Purpose:** Cache media blobs with reference counting 483 + 484 + ```typescript 485 + class BlobCache { 486 + private store = new Map<string, BlobCacheEntry>(); 487 + private inFlight = new Map<string, InFlightBlobEntry>(); 488 + 489 + ensure(did, cid, loader) { 490 + // Already cached? 491 + const cached = this.get(did, cid); 492 + if (cached) return { promise: Promise.resolve(cached), release: noop }; 493 + 494 + // In-flight request? 495 + const existing = this.inFlight.get(key); 496 + if (existing) { 497 + existing.refCount++; // Multiple consumers 498 + return { promise: existing.promise, release: () => this.release(key) }; 499 + } 500 + 501 + // New request 502 + const { promise, abort } = loader(); 503 + this.inFlight.set(key, { promise, abort, refCount: 1 }); 504 + return { promise, release: () => this.release(key) }; 505 + } 506 + 507 + private release(key) { 508 + const entry = this.inFlight.get(key); 509 + entry.refCount--; 510 + if (entry.refCount <= 0) { 511 + this.inFlight.delete(key); 512 + entry.abort(); // Cancel fetch 513 + } 514 + } 515 + } 516 + ``` 517 + 518 + ### RecordCache 519 + **Purpose:** Cache AT Protocol records with deduplication 520 + 521 + Identical structure to BlobCache but for record data. 522 + 523 + --- 524 + 525 + ## Common Patterns 526 + 527 + ### 1. Cancellation Pattern 528 + ```typescript 529 + useEffect(() => { 530 + let cancelled = false; 531 + 532 + const assignState = (next) => { 533 + if (cancelled) return; // Don't update unmounted component 534 + setState(prev => ({ ...prev, ...next })); 535 + }; 536 + 537 + // ... async work ... 538 + 539 + return () => { 540 + cancelled = true; // Mark as cancelled 541 + release?.(); // Decrement refCount 542 + }; 543 + }, [deps]); 544 + ``` 545 + 546 + ### 2. Error Stabilization Pattern 547 + ```typescript 548 + setError(prevError => 549 + prevError?.message === newError.message 550 + ? prevError // Reuse same reference 551 + : newError // New error 552 + ); 553 + ``` 554 + 555 + ### 3. Identity Tracking Pattern 556 + ```typescript 557 + const identityRef = useRef<string>(); 558 + const identity = did && endpoint ? `${did}::${endpoint}` : undefined; 559 + 560 + useEffect(() => { 561 + if (identityRef.current !== identity) { 562 + identityRef.current = identity; 563 + resetState(); // Clear stale data 564 + } 565 + // ... 566 + }, [identity]); 567 + ``` 568 + 569 + ### 4. Dual-Mode Resolution 570 + ```typescript 571 + const isDid = input.startsWith("did:"); 572 + const normalizedHandle = !isDid ? input.toLowerCase() : undefined; 573 + 574 + // Different code paths 575 + if (isDid) { 576 + snapshot = await didCache.ensureDidDoc(resolver, input); 577 + } else { 578 + snapshot = await didCache.ensureHandle(resolver, normalizedHandle); 579 + } 580 + ``` 581 + 582 + --- 583 + 584 + ## Performance Optimizations 585 + 586 + ### 1. **Memoized Snapshots** 587 + Caches return stable references when data unchanged โ†’ prevents re-renders 588 + 589 + ### 2. **Reference Counting** 590 + Multiple components requesting same data share one fetch 591 + 592 + ### 3. **Prefetching** 593 + `usePaginatedRecords` loads next page in background 594 + 595 + ### 4. **CDN URLs** 596 + Bluesky appview returns CDN URLs โ†’ skip blob fetching for images 597 + 598 + ### 5. **Smart Routing** 599 + Bluesky collections use fast appview โ†’ non-Bluesky goes direct to PDS 600 + 601 + ### 6. **Request Deduplication** 602 + In-flight request maps prevent duplicate fetches 603 + 604 + ### 7. **Timestamp Validation** 605 + Skip invalid records early (before 2023) โ†’ fewer wasted cycles 606 + 607 + --- 608 + 609 + ## Error Handling Strategy 610 + 611 + ### 1. **Fallback Chains** 612 + Never fail on first attempt โ†’ try multiple sources 613 + 614 + ### 2. **Graceful Degradation** 615 + ```typescript 616 + // Slingshot failed? Try appview 617 + try { 618 + return await fetchFromSlingshot(); 619 + } catch (slingshotError) { 620 + try { 621 + return await fetchFromAppview(); 622 + } catch (appviewError) { 623 + // Combine errors for better debugging 624 + throw new Error(`${appviewError.message}; Slingshot: ${slingshotError.message}`); 625 + } 626 + } 627 + ``` 628 + 629 + ### 3. **Component Isolation** 630 + Errors in one component don't crash others (via error boundaries recommended) 631 + 632 + ### 4. **Abort Handling** 633 + ```typescript 634 + try { 635 + await fetch(url, { signal }); 636 + } catch (err) { 637 + if (err.name === "AbortError") return; // Expected, ignore 638 + throw err; 639 + } 640 + ``` 641 + 642 + ### 5. **Banned Bluesky Account Detection** 643 + When all three tiers fail and the PDS is a `.bsky.network` endpoint, provide a helpful error: 644 + ```typescript 645 + // All tiers failed - check if it's a banned Bluesky account 646 + if (pdsEndpoint.includes('.bsky.network')) { 647 + throw new Error( 648 + 'Record unavailable. The Bluesky PDS may be unreachable or the account may be banned.' 649 + ); 650 + } 651 + ``` 652 + 653 + This helps users understand why data is unavailable instead of showing generic fetch errors. Applies to both `useBlueskyAppview` and `useAtProtoRecord` hooks. 654 + 655 + --- 656 + 657 + ## Testing Considerations 658 + 659 + ### Key scenarios to test: 660 + 1. **Concurrent requests:** Multiple components requesting same data 661 + 2. **Race conditions:** Component unmounting mid-fetch 662 + 3. **Cache invalidation:** Identity changes during fetch 663 + 4. **Error fallbacks:** Slingshot down โ†’ appview works 664 + 5. **Timestamp filtering:** Records before 2023 skipped 665 + 6. **Reference counting:** Proper cleanup on unmount 666 + 7. **Prefetching:** Background loads don't interfere with active loads 667 + 668 + --- 669 + 670 + ## Common Gotchas 671 + 672 + ### 1. **React Rules of Hooks** 673 + All hooks called unconditionally, even if results not used: 674 + ```typescript 675 + // Always call, conditionally use results 676 + const blueskyResult = useBlueskyAppview({ 677 + did: isBlueskyCollection ? handleOrDid : undefined, // Pass undefined to skip 678 + collection: isBlueskyCollection ? collection : undefined, 679 + rkey: isBlueskyCollection ? rkey : undefined, 680 + }); 681 + ``` 682 + 683 + ### 2. **Cleanup Order Matters** 684 + ```typescript 685 + return () => { 686 + cancelled = true; // 1. Prevent state updates 687 + release?.(); // 2. Decrement refCount 688 + revokeObjectURL(...); // 3. Free resources 689 + }; 690 + ``` 691 + 692 + ### 3. **Snapshot Reuse** 693 + Don't modify cached snapshots! They're shared across components. 694 + 695 + ### 4. **CDN URL Extraction** 696 + Bluesky CDN URLs must be parsed carefully: 697 + ``` 698 + https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg 699 + ^^^^^^^^^^^^ ^^^^^^ 700 + DID CID 701 + ```
+41 -3
README.md
··· 1 # atproto-ui 2 3 - A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically. [Live demo](https://atproto-ui.wisp.place). 4 5 ## Screenshots 6 ··· 9 10 ## Features 11 12 - - **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledString`, `LeafletDocument`) 13 - **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching) 14 - **Customizable theming** - Override CSS variables to match your app's design 15 - **Composable hooks** - Build custom renderers with protocol primitives 16 - Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients ··· 111 <LeafletDocument did={did} rkey={rkey} record={documentRecord} /> 112 ``` 113 114 ## API Reference 115 116 ### Components ··· 181 182 ## Demo 183 184 - Check out the [live demo](https://atproto-ui.netlify.app/) to see all components in action. 185 186 ### Running Locally 187
··· 1 # atproto-ui 2 3 + A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically as well as caching these so multiple components can render quickly. [Live demo](https://atproto-ui.wisp.place). 4 + 5 + This project is mostly a wrapper on the extremely amazing work [Mary](https://mary.my.id/) has done with [atcute](https://tangled.org/@mary.my.id/atcute), please support it. I have to give thanks to [phil](https://bsky.app/profile/bad-example.com) for microcosm and slingshot. Incredible services being given for free that is responsible for why the components fetch data so quickly. 6 7 ## Screenshots 8 ··· 11 12 ## Features 13 14 + - **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledRepo`, `LeafletDocument`) 15 - **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching) 16 + - **Caching** - Blobs, DIDs, and records are cached so components which use the same ones can render even quicker 17 - **Customizable theming** - Override CSS variables to match your app's design 18 - **Composable hooks** - Build custom renderers with protocol primitives 19 - Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients ··· 114 <LeafletDocument did={did} rkey={rkey} record={documentRecord} /> 115 ``` 116 117 + ### Using atcute Directly 118 + 119 + Use atcute directly to construct records and pass them to componentsโ€”fully compatible! 120 + 121 + ```tsx 122 + import { Client, simpleFetchHandler, ok } from '@atcute/client'; 123 + import type { AppBskyFeedPost } from '@atcute/bluesky'; 124 + import { BlueskyPost } from 'atproto-ui'; 125 + 126 + // Create atcute client 127 + const client = new Client({ 128 + handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 129 + }); 130 + 131 + // Fetch a record 132 + const data = await ok( 133 + client.get('com.atproto.repo.getRecord', { 134 + params: { 135 + repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq', 136 + collection: 'app.bsky.feed.post', 137 + rkey: '3m45rq4sjes2h' 138 + } 139 + }) 140 + ); 141 + 142 + const record = data.value as AppBskyFeedPost.Main; 143 + 144 + // Pass atcute record directly to component! 145 + <BlueskyPost 146 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 147 + rkey="3m45rq4sjes2h" 148 + record={record} 149 + /> 150 + ``` 151 + 152 ## API Reference 153 154 ### Components ··· 219 220 ## Demo 221 222 + Check out the [live demo](https://atproto-ui.wisp.place/) to see all components in action. 223 224 ### Running Locally 225
+697
bun.lock
···
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "atproto-ui", 7 + "dependencies": { 8 + "@atcute/atproto": "^3.1.7", 9 + "@atcute/bluesky": "^3.2.3", 10 + "@atcute/client": "^4.0.3", 11 + "@atcute/identity-resolver": "^1.1.3", 12 + "@atcute/tangled": "^1.0.10", 13 + }, 14 + "devDependencies": { 15 + "@eslint/js": "^9.36.0", 16 + "@microsoft/api-extractor": "^7.53.1", 17 + "@types/node": "^24.6.0", 18 + "@types/react": "^19.1.16", 19 + "@types/react-dom": "^19.1.9", 20 + "@vitejs/plugin-react": "^5.0.4", 21 + "eslint": "^9.36.0", 22 + "eslint-plugin-react-hooks": "^5.2.0", 23 + "eslint-plugin-react-refresh": "^0.4.22", 24 + "globals": "^16.4.0", 25 + "react": "^19.1.1", 26 + "react-dom": "^19.1.1", 27 + "rollup-plugin-typescript2": "^0.36.0", 28 + "typescript": "~5.9.3", 29 + "typescript-eslint": "^8.45.0", 30 + "unplugin-dts": "^1.0.0-beta.6", 31 + "vite": "npm:rolldown-vite@7.1.14", 32 + }, 33 + "peerDependencies": { 34 + "react": "^18.2.0 || ^19.0.0", 35 + "react-dom": "^18.2.0 || ^19.0.0", 36 + }, 37 + "optionalPeers": [ 38 + "react-dom", 39 + ], 40 + }, 41 + }, 42 + "packages": { 43 + "@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="], 44 + 45 + "@atcute/bluesky": ["@atcute/bluesky@3.2.11", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A=="], 46 + 47 + "@atcute/client": ["@atcute/client@4.1.0", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ=="], 48 + 49 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 50 + 51 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.4", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@atcute/util-fetch": "^1.0.3", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA=="], 52 + 53 + "@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="], 54 + 55 + "@atcute/tangled": ["@atcute/tangled@1.0.12", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.3" } }, "sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA=="], 56 + 57 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.4", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg=="], 58 + 59 + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], 60 + 61 + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], 62 + 63 + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], 64 + 65 + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], 66 + 67 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], 68 + 69 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 70 + 71 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], 72 + 73 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], 74 + 75 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], 76 + 77 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 78 + 79 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 80 + 81 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 82 + 83 + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], 84 + 85 + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], 86 + 87 + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], 88 + 89 + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], 90 + 91 + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], 92 + 93 + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], 94 + 95 + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], 96 + 97 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 98 + 99 + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], 100 + 101 + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], 102 + 103 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 104 + 105 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 106 + 107 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 108 + 109 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 110 + 111 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 112 + 113 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 114 + 115 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], 116 + 117 + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], 118 + 119 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 120 + 121 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 122 + 123 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 124 + 125 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 126 + 127 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 128 + 129 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 130 + 131 + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], 132 + 133 + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], 134 + 135 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 136 + 137 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 138 + 139 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 140 + 141 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 142 + 143 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 144 + 145 + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.55.1", "", { "dependencies": { "@microsoft/api-extractor-model": "7.32.1", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0", "@rushstack/rig-package": "0.6.0", "@rushstack/terminal": "0.19.4", "@rushstack/ts-command-line": "5.1.4", "diff": "~8.0.2", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w=="], 146 + 147 + "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.32.1", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0" } }, "sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA=="], 148 + 149 + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], 150 + 151 + "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.0", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.12.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw=="], 152 + 153 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], 154 + 155 + "@oxc-project/runtime": ["@oxc-project/runtime@0.92.0", "", {}, "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw=="], 156 + 157 + "@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="], 158 + 159 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="], 160 + 161 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="], 162 + 163 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ=="], 164 + 165 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.41", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g=="], 166 + 167 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm" }, "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA=="], 168 + 169 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg=="], 170 + 171 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg=="], 172 + 173 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g=="], 174 + 175 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q=="], 176 + 177 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.41", "", { "os": "none", "cpu": "arm64" }, "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg=="], 178 + 179 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.41", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ=="], 180 + 181 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "arm64" }, "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg=="], 182 + 183 + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "ia32" }, "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw=="], 184 + 185 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "x64" }, "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg=="], 186 + 187 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="], 188 + 189 + "@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], 190 + 191 + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="], 192 + 193 + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="], 194 + 195 + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="], 196 + 197 + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="], 198 + 199 + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="], 200 + 201 + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="], 202 + 203 + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="], 204 + 205 + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="], 206 + 207 + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="], 208 + 209 + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="], 210 + 211 + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="], 212 + 213 + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="], 214 + 215 + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="], 216 + 217 + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="], 218 + 219 + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="], 220 + 221 + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="], 222 + 223 + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="], 224 + 225 + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="], 226 + 227 + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="], 228 + 229 + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="], 230 + 231 + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="], 232 + 233 + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="], 234 + 235 + "@rushstack/node-core-library": ["@rushstack/node-core-library@5.19.0", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A=="], 236 + 237 + "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.1.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA=="], 238 + 239 + "@rushstack/rig-package": ["@rushstack/rig-package@0.6.0", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw=="], 240 + 241 + "@rushstack/terminal": ["@rushstack/terminal@0.19.4", "", { "dependencies": { "@rushstack/node-core-library": "5.19.0", "@rushstack/problem-matcher": "0.1.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ=="], 242 + 243 + "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.1.4", "", { "dependencies": { "@rushstack/terminal": "0.19.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA=="], 244 + 245 + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], 246 + 247 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 248 + 249 + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], 250 + 251 + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], 252 + 253 + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], 254 + 255 + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], 256 + 257 + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], 258 + 259 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 260 + 261 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 262 + 263 + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], 264 + 265 + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], 266 + 267 + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], 268 + 269 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="], 270 + 271 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="], 272 + 273 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="], 274 + 275 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="], 276 + 277 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="], 278 + 279 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="], 280 + 281 + "@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], 282 + 283 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="], 284 + 285 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="], 286 + 287 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], 288 + 289 + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="], 290 + 291 + "@volar/language-core": ["@volar/language-core@2.4.26", "", { "dependencies": { "@volar/source-map": "2.4.26" } }, "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A=="], 292 + 293 + "@volar/source-map": ["@volar/source-map@2.4.26", "", {}, "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw=="], 294 + 295 + "@volar/typescript": ["@volar/typescript@2.4.26", "", { "dependencies": { "@volar/language-core": "2.4.26", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA=="], 296 + 297 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 298 + 299 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 300 + 301 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 302 + 303 + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], 304 + 305 + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], 306 + 307 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 308 + 309 + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], 310 + 311 + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], 312 + 313 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 314 + 315 + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="], 316 + 317 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 318 + 319 + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], 320 + 321 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 322 + 323 + "caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], 324 + 325 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 326 + 327 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 328 + 329 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 330 + 331 + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], 332 + 333 + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], 334 + 335 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 336 + 337 + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], 338 + 339 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 340 + 341 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 342 + 343 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 344 + 345 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 346 + 347 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 348 + 349 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 350 + 351 + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], 352 + 353 + "electron-to-chromium": ["electron-to-chromium@1.5.263", "", {}, "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg=="], 354 + 355 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 356 + 357 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 358 + 359 + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], 360 + 361 + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], 362 + 363 + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="], 364 + 365 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 366 + 367 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 368 + 369 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 370 + 371 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 372 + 373 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 374 + 375 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 376 + 377 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 378 + 379 + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 380 + 381 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 382 + 383 + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], 384 + 385 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 386 + 387 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 388 + 389 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 390 + 391 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 392 + 393 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 394 + 395 + "find-cache-dir": ["find-cache-dir@3.3.2", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" } }, "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig=="], 396 + 397 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 398 + 399 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 400 + 401 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 402 + 403 + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], 404 + 405 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 406 + 407 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 408 + 409 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 410 + 411 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 412 + 413 + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], 414 + 415 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 416 + 417 + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 418 + 419 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 420 + 421 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 422 + 423 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 424 + 425 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 426 + 427 + "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], 428 + 429 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 430 + 431 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 432 + 433 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 434 + 435 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 436 + 437 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 438 + 439 + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], 440 + 441 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 442 + 443 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 444 + 445 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 446 + 447 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 448 + 449 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 450 + 451 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 452 + 453 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 454 + 455 + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], 456 + 457 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 458 + 459 + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], 460 + 461 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 462 + 463 + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], 464 + 465 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], 466 + 467 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], 468 + 469 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], 470 + 471 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], 472 + 473 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], 474 + 475 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], 476 + 477 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], 478 + 479 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], 480 + 481 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], 482 + 483 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], 484 + 485 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], 486 + 487 + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], 488 + 489 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 490 + 491 + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], 492 + 493 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 494 + 495 + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], 496 + 497 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 498 + 499 + "make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], 500 + 501 + "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], 502 + 503 + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], 504 + 505 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 506 + 507 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 508 + 509 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 510 + 511 + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], 512 + 513 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 514 + 515 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 516 + 517 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 518 + 519 + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], 520 + 521 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 522 + 523 + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], 524 + 525 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 526 + 527 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 528 + 529 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 530 + 531 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 532 + 533 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 534 + 535 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 536 + 537 + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], 538 + 539 + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], 540 + 541 + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 542 + 543 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 544 + 545 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 546 + 547 + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], 548 + 549 + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], 550 + 551 + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], 552 + 553 + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], 554 + 555 + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], 556 + 557 + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], 558 + 559 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 560 + 561 + "rolldown": ["rolldown@1.0.0-beta.41", "", { "dependencies": { "@oxc-project/types": "=0.93.0", "@rolldown/pluginutils": "1.0.0-beta.41", "ansis": "=4.2.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-x64": "1.0.0-beta.41", "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg=="], 562 + 563 + "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], 564 + 565 + "rollup-plugin-typescript2": ["rollup-plugin-typescript2@0.36.0", "", { "dependencies": { "@rollup/pluginutils": "^4.1.2", "find-cache-dir": "^3.3.2", "fs-extra": "^10.0.0", "semver": "^7.5.4", "tslib": "^2.6.2" }, "peerDependencies": { "rollup": ">=1.26.3", "typescript": ">=2.4.0" } }, "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw=="], 566 + 567 + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], 568 + 569 + "semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], 570 + 571 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 572 + 573 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 574 + 575 + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 576 + 577 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 578 + 579 + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], 580 + 581 + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], 582 + 583 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 584 + 585 + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], 586 + 587 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 588 + 589 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 590 + 591 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 592 + 593 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 594 + 595 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 596 + 597 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 598 + 599 + "typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="], 600 + 601 + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], 602 + 603 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 604 + 605 + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], 606 + 607 + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], 608 + 609 + "unplugin-dts": ["unplugin-dts@1.0.0-beta.6", "", { "dependencies": { "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.17", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", "unplugin": "^2.3.2" }, "peerDependencies": { "@microsoft/api-extractor": ">=7", "@rspack/core": "^1", "@vue/language-core": "~3.0.1", "esbuild": "*", "rolldown": "*", "rollup": ">=3", "typescript": ">=4", "vite": ">=3", "webpack": "^4 || ^5" }, "optionalPeers": ["@microsoft/api-extractor", "@rspack/core", "@vue/language-core", "esbuild", "rolldown", "rollup", "vite", "webpack"] }, "sha512-+xbFv5aVFtLZFNBAKI4+kXmd2h+T42/AaP8Bsp0YP/je/uOTN94Ame2Xt3e9isZS+Z7/hrLCLbsVJh+saqFMfQ=="], 610 + 611 + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], 612 + 613 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 614 + 615 + "vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="], 616 + 617 + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], 618 + 619 + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], 620 + 621 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 622 + 623 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 624 + 625 + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], 626 + 627 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 628 + 629 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 630 + 631 + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 632 + 633 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 634 + 635 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 636 + 637 + "@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 638 + 639 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 640 + 641 + "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 642 + 643 + "@microsoft/api-extractor/typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], 644 + 645 + "@microsoft/tsdoc-config/ajv": ["ajv@8.12.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA=="], 646 + 647 + "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 648 + 649 + "@rushstack/node-core-library/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="], 650 + 651 + "@rushstack/node-core-library/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], 652 + 653 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 654 + 655 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 656 + 657 + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 658 + 659 + "ajv-formats/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="], 660 + 661 + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 662 + 663 + "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 664 + 665 + "js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 666 + 667 + "make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 668 + 669 + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], 670 + 671 + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], 672 + 673 + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="], 674 + 675 + "rollup-plugin-typescript2/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 676 + 677 + "unplugin-dts/@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], 678 + 679 + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 680 + 681 + "@microsoft/tsdoc-config/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 682 + 683 + "@rushstack/node-core-library/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 684 + 685 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 686 + 687 + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 688 + 689 + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 690 + 691 + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], 692 + 693 + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], 694 + 695 + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], 696 + } 697 + }
+308 -111
lib/components/BlueskyPostList.tsx
··· 4 type AuthorFeedReason, 5 type ReplyParentInfo, 6 } from "../hooks/usePaginatedRecords"; 7 - import type { FeedPostRecord } from "../types/bluesky"; 8 import { useDidResolution } from "../hooks/useDidResolution"; 9 import { BlueskyIcon } from "./BlueskyIcon"; 10 import { parseAtUri } from "../utils/at-uri"; 11 12 /** 13 * Options for rendering a paginated list of Bluesky posts. ··· 72 73 if (error) 74 return ( 75 - <div style={{ padding: 8, color: "crimson" }}> 76 Failed to load posts. 77 </div> 78 ); ··· 116 record={record.value} 117 rkey={record.rkey} 118 did={actorPath} 119 reason={record.reason} 120 replyParent={record.replyParent} 121 hasDivider={idx < records.length - 1} ··· 202 record: FeedPostRecord; 203 rkey: string; 204 did: string; 205 reason?: AuthorFeedReason; 206 replyParent?: ReplyParentInfo; 207 hasDivider: boolean; ··· 211 record, 212 rkey, 213 did, 214 reason, 215 replyParent, 216 hasDivider, 217 }) => { 218 const text = record.text?.trim() ?? ""; 219 const relative = record.createdAt 220 ? formatRelativeTime(record.createdAt) ··· 222 const absolute = record.createdAt 223 ? new Date(record.createdAt).toLocaleString() 224 : undefined; 225 - const href = `https://bsky.app/profile/${did}/post/${rkey}`; 226 - const repostLabel = 227 - reason?.$type === "app.bsky.feed.defs#reasonRepost" 228 - ? `${formatActor(reason.by) ?? "Someone"} reposted` 229 - : undefined; 230 const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 231 - const parentDid = 232 - replyParent?.author?.did ?? 233 - (parentUri ? parseAtUri(parentUri)?.did : undefined); 234 - const { handle: resolvedReplyHandle } = useDidResolution( 235 replyParent?.author?.handle ? undefined : parentDid, 236 ); 237 - const replyLabel = formatReplyTarget( 238 - parentUri, 239 - replyParent, 240 - resolvedReplyHandle, 241 ); 242 243 return ( 244 - <a 245 - href={href} 246 - target="_blank" 247 - rel="noopener noreferrer" 248 style={{ 249 - ...listStyles.row, 250 - color: `var(--atproto-color-text)`, 251 - borderBottom: hasDivider 252 - ? `1px solid var(--atproto-color-border)` 253 - : "none", 254 }} 255 > 256 - {repostLabel && ( 257 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 258 - {repostLabel} 259 - </span> 260 )} 261 - {replyLabel && ( 262 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 263 - {replyLabel} 264 - </span> 265 )} 266 - {relative && ( 267 - <span 268 - style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)` }} 269 - title={absolute} 270 - > 271 - {relative} 272 - </span> 273 - )} 274 - {text && ( 275 - <p style={{ ...listStyles.rowBody, color: `var(--atproto-color-text)` }}> 276 - {text} 277 - </p> 278 - )} 279 - {!text && ( 280 - <p 281 - style={{ 282 - ...listStyles.rowBody, 283 - color: `var(--atproto-color-text)`, 284 - fontStyle: "italic", 285 - }} 286 - > 287 - No text content. 288 - </p> 289 - )} 290 - </a> 291 ); 292 }; 293 ··· 352 display: "flex", 353 alignItems: "center", 354 justifyContent: "center", 355 - //background: 'rgba(17, 133, 254, 0.14)', 356 borderRadius: "50%", 357 } satisfies React.CSSProperties, 358 headerText: { ··· 380 fontSize: 13, 381 textAlign: "center", 382 } satisfies React.CSSProperties, 383 - row: { 384 - padding: "18px", 385 - textDecoration: "none", 386 display: "flex", 387 flexDirection: "column", 388 - gap: 6, 389 transition: "background-color 120ms ease", 390 } satisfies React.CSSProperties, 391 - rowHeader: { 392 display: "flex", 393 - gap: 6, 394 - alignItems: "baseline", 395 fontSize: 13, 396 } satisfies React.CSSProperties, 397 - rowTime: { 398 - fontSize: 12, 399 fontWeight: 500, 400 } satisfies React.CSSProperties, 401 - rowMeta: { 402 - fontSize: 12, 403 fontWeight: 500, 404 - letterSpacing: "0.6px", 405 } satisfies React.CSSProperties, 406 - rowBody: { 407 margin: 0, 408 whiteSpace: "pre-wrap", 409 - fontSize: 14, 410 - lineHeight: 1.45, 411 } satisfies React.CSSProperties, 412 footer: { 413 display: "flex", ··· 416 padding: "12px 18px", 417 borderTop: "1px solid transparent", 418 fontSize: 13, 419 - } satisfies React.CSSProperties, 420 - navButton: { 421 - border: "none", 422 - borderRadius: 999, 423 - padding: "6px 12px", 424 - fontSize: 13, 425 - fontWeight: 500, 426 - background: "transparent", 427 - display: "flex", 428 - alignItems: "center", 429 - gap: 4, 430 - transition: "background-color 120ms ease", 431 } satisfies React.CSSProperties, 432 pageChips: { 433 display: "flex", ··· 472 }; 473 474 export default BlueskyPostList; 475 - 476 - function formatActor(actor?: { handle?: string; did?: string }) { 477 - if (!actor) return undefined; 478 - if (actor.handle) return `@${actor.handle}`; 479 - if (actor.did) return `@${formatDid(actor.did)}`; 480 - return undefined; 481 - } 482 - 483 - function formatReplyTarget( 484 - parentUri?: string, 485 - feedParent?: ReplyParentInfo, 486 - resolvedHandle?: string, 487 - ) { 488 - const directHandle = feedParent?.author?.handle; 489 - const handle = directHandle ?? resolvedHandle; 490 - if (handle) { 491 - return `Replying to @${handle}`; 492 - } 493 - const parentDid = feedParent?.author?.did; 494 - const targetUri = feedParent?.uri ?? parentUri; 495 - if (!targetUri) return undefined; 496 - const parsed = parseAtUri(targetUri); 497 - const did = parentDid ?? parsed?.did; 498 - if (!did) return undefined; 499 - return `Replying to @${formatDid(did)}`; 500 - }
··· 4 type AuthorFeedReason, 5 type ReplyParentInfo, 6 } from "../hooks/usePaginatedRecords"; 7 + import type { FeedPostRecord, ProfileRecord } from "../types/bluesky"; 8 import { useDidResolution } from "../hooks/useDidResolution"; 9 import { BlueskyIcon } from "./BlueskyIcon"; 10 import { parseAtUri } from "../utils/at-uri"; 11 + import { useAtProto } from "../providers/AtProtoProvider"; 12 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 13 + import { useBlob } from "../hooks/useBlob"; 14 + import { getAvatarCid } from "../utils/profile"; 15 + import { isBlobWithCdn } from "../utils/blob"; 16 + import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile"; 17 + import { RichText as BlueskyRichText } from "./RichText"; 18 19 /** 20 * Options for rendering a paginated list of Bluesky posts. ··· 79 80 if (error) 81 return ( 82 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 83 Failed to load posts. 84 </div> 85 ); ··· 123 record={record.value} 124 rkey={record.rkey} 125 did={actorPath} 126 + uri={record.uri} 127 reason={record.reason} 128 replyParent={record.replyParent} 129 hasDivider={idx < records.length - 1} ··· 210 record: FeedPostRecord; 211 rkey: string; 212 did: string; 213 + uri?: string; 214 reason?: AuthorFeedReason; 215 replyParent?: ReplyParentInfo; 216 hasDivider: boolean; ··· 220 record, 221 rkey, 222 did, 223 + uri, 224 reason, 225 replyParent, 226 hasDivider, 227 }) => { 228 + const { blueskyAppBaseUrl } = useAtProto(); 229 const text = record.text?.trim() ?? ""; 230 const relative = record.createdAt 231 ? formatRelativeTime(record.createdAt) ··· 233 const absolute = record.createdAt 234 ? new Date(record.createdAt).toLocaleString() 235 : undefined; 236 + 237 + // Parse the URI to get the actual post's DID and rkey 238 + const parsedUri = uri ? parseAtUri(uri) : undefined; 239 + const postDid = parsedUri?.did ?? did; 240 + const postRkey = parsedUri?.rkey ?? rkey; 241 + const href = `${blueskyAppBaseUrl}/profile/${postDid}/post/${postRkey}`; 242 + 243 + // Author profile and avatar 244 + const { handle: authorHandle } = useDidResolution(postDid); 245 + const { record: authorProfile } = useAtProtoRecord<ProfileRecord>({ 246 + did: postDid, 247 + collection: BLUESKY_PROFILE_COLLECTION, 248 + rkey: "self", 249 + }); 250 + const authorDisplayName = authorProfile?.displayName; 251 + const authorAvatar = authorProfile?.avatar; 252 + const authorAvatarCdnUrl = isBlobWithCdn(authorAvatar) ? authorAvatar.cdnUrl : undefined; 253 + const authorAvatarCid = authorAvatarCdnUrl ? undefined : getAvatarCid(authorProfile); 254 + const { url: authorAvatarUrl } = useBlob( 255 + postDid, 256 + authorAvatarCid, 257 + ); 258 + const finalAuthorAvatarUrl = authorAvatarCdnUrl ?? authorAvatarUrl; 259 + 260 + // Repost metadata 261 + const isRepost = reason?.$type === "app.bsky.feed.defs#reasonRepost"; 262 + const reposterDid = reason?.by?.did; 263 + const { handle: reposterHandle } = useDidResolution(reposterDid); 264 + const { record: reposterProfile } = useAtProtoRecord<ProfileRecord>({ 265 + did: reposterDid, 266 + collection: BLUESKY_PROFILE_COLLECTION, 267 + rkey: "self", 268 + }); 269 + const reposterDisplayName = reposterProfile?.displayName; 270 + const reposterAvatar = reposterProfile?.avatar; 271 + const reposterAvatarCdnUrl = isBlobWithCdn(reposterAvatar) ? reposterAvatar.cdnUrl : undefined; 272 + const reposterAvatarCid = reposterAvatarCdnUrl ? undefined : getAvatarCid(reposterProfile); 273 + const { url: reposterAvatarUrl } = useBlob( 274 + reposterDid, 275 + reposterAvatarCid, 276 + ); 277 + const finalReposterAvatarUrl = reposterAvatarCdnUrl ?? reposterAvatarUrl; 278 + 279 + // Reply metadata 280 const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 281 + const parentDid = replyParent?.author?.did ?? (parentUri ? parseAtUri(parentUri)?.did : undefined); 282 + const { handle: parentHandle } = useDidResolution( 283 replyParent?.author?.handle ? undefined : parentDid, 284 ); 285 + const { record: parentProfile } = useAtProtoRecord<ProfileRecord>({ 286 + did: parentDid, 287 + collection: BLUESKY_PROFILE_COLLECTION, 288 + rkey: "self", 289 + }); 290 + const parentAvatar = parentProfile?.avatar; 291 + const parentAvatarCdnUrl = isBlobWithCdn(parentAvatar) ? parentAvatar.cdnUrl : undefined; 292 + const parentAvatarCid = parentAvatarCdnUrl ? undefined : getAvatarCid(parentProfile); 293 + const { url: parentAvatarUrl } = useBlob( 294 + parentDid, 295 + parentAvatarCid, 296 ); 297 + const finalParentAvatarUrl = parentAvatarCdnUrl ?? parentAvatarUrl; 298 + 299 + const isReply = !!parentUri; 300 + const replyTargetHandle = replyParent?.author?.handle ?? parentHandle; 301 + 302 + const postPreview = text.slice(0, 100); 303 + const ariaLabel = text 304 + ? `Post by ${authorDisplayName ?? authorHandle ?? did}: ${postPreview}${text.length > 100 ? "..." : ""}` 305 + : `Post by ${authorDisplayName ?? authorHandle ?? did}`; 306 307 return ( 308 + <div 309 style={{ 310 + ...listStyles.rowContainer, 311 + borderBottom: hasDivider ? `1px solid var(--atproto-color-border)` : "none", 312 }} 313 > 314 + {isRepost && ( 315 + <div style={listStyles.repostIndicator}> 316 + {finalReposterAvatarUrl && ( 317 + <img 318 + src={finalReposterAvatarUrl} 319 + alt="" 320 + style={listStyles.repostAvatar} 321 + /> 322 + )} 323 + <svg 324 + width="16" 325 + height="16" 326 + viewBox="0 0 16 16" 327 + fill="none" 328 + style={{ flexShrink: 0 }} 329 + > 330 + <path 331 + d="M5.5 3.5L3 6L5.5 8.5M3 6H10C11.1046 6 12 6.89543 12 8V8.5M10.5 12.5L13 10L10.5 7.5M13 10H6C4.89543 10 4 9.10457 4 8V7.5" 332 + stroke="var(--atproto-color-text-secondary)" 333 + strokeWidth="1.5" 334 + strokeLinecap="round" 335 + strokeLinejoin="round" 336 + /> 337 + </svg> 338 + <span style={{ ...listStyles.repostText, color: "var(--atproto-color-text-secondary)" }}> 339 + {reposterDisplayName ?? reposterHandle ?? "Someone"} reposted 340 + </span> 341 + </div> 342 )} 343 + 344 + {isReply && ( 345 + <div style={listStyles.replyIndicator}> 346 + <svg 347 + width="14" 348 + height="14" 349 + viewBox="0 0 14 14" 350 + fill="none" 351 + style={{ flexShrink: 0 }} 352 + > 353 + <path 354 + d="M11 7H3M3 7L7 3M3 7L7 11" 355 + stroke="#1185FE" 356 + strokeWidth="1.5" 357 + strokeLinecap="round" 358 + strokeLinejoin="round" 359 + /> 360 + </svg> 361 + <span style={{ ...listStyles.replyText, color: "var(--atproto-color-text-secondary)" }}> 362 + Replying to 363 + </span> 364 + {finalParentAvatarUrl && ( 365 + <img 366 + src={finalParentAvatarUrl} 367 + alt="" 368 + style={listStyles.replyAvatar} 369 + /> 370 + )} 371 + <span style={{ color: "#1185FE", fontWeight: 600 }}> 372 + @{replyTargetHandle ?? formatDid(parentDid ?? "")} 373 + </span> 374 + </div> 375 )} 376 + 377 + <div style={listStyles.postContent}> 378 + <div style={listStyles.avatarContainer}> 379 + {finalAuthorAvatarUrl ? ( 380 + <img 381 + src={finalAuthorAvatarUrl} 382 + alt={authorDisplayName ?? authorHandle ?? "User avatar"} 383 + style={listStyles.avatar} 384 + /> 385 + ) : ( 386 + <div style={listStyles.avatarPlaceholder}> 387 + {(authorDisplayName ?? authorHandle ?? "?")[0].toUpperCase()} 388 + </div> 389 + )} 390 + </div> 391 + 392 + <div style={listStyles.postMain}> 393 + <div style={listStyles.postHeader}> 394 + <a 395 + href={`${blueskyAppBaseUrl}/profile/${postDid}`} 396 + target="_blank" 397 + rel="noopener noreferrer" 398 + style={{ ...listStyles.authorName, color: "var(--atproto-color-text)" }} 399 + onClick={(e) => e.stopPropagation()} 400 + > 401 + {authorDisplayName ?? authorHandle ?? formatDid(postDid)} 402 + </a> 403 + <span style={{ ...listStyles.authorHandle, color: "var(--atproto-color-text-secondary)" }}> 404 + @{authorHandle ?? formatDid(postDid)} 405 + </span> 406 + <span style={{ ...listStyles.separator, color: "var(--atproto-color-text-secondary)" }}>ยท</span> 407 + <span 408 + style={{ ...listStyles.timestamp, color: "var(--atproto-color-text-secondary)" }} 409 + title={absolute} 410 + > 411 + {relative} 412 + </span> 413 + </div> 414 + 415 + <a 416 + href={href} 417 + target="_blank" 418 + rel="noopener noreferrer" 419 + aria-label={ariaLabel} 420 + style={{ ...listStyles.postLink, color: "var(--atproto-color-text)" }} 421 + > 422 + {text && ( 423 + <p style={listStyles.postText}> 424 + <BlueskyRichText text={text} facets={record.facets} /> 425 + </p> 426 + )} 427 + {!text && ( 428 + <p style={{ ...listStyles.postText, fontStyle: "italic", color: "var(--atproto-color-text-secondary)" }}> 429 + No text content 430 + </p> 431 + )} 432 + </a> 433 + </div> 434 + </div> 435 + </div> 436 ); 437 }; 438 ··· 497 display: "flex", 498 alignItems: "center", 499 justifyContent: "center", 500 borderRadius: "50%", 501 } satisfies React.CSSProperties, 502 headerText: { ··· 524 fontSize: 13, 525 textAlign: "center", 526 } satisfies React.CSSProperties, 527 + rowContainer: { 528 + padding: "16px", 529 display: "flex", 530 flexDirection: "column", 531 + gap: 8, 532 transition: "background-color 120ms ease", 533 + position: "relative", 534 } satisfies React.CSSProperties, 535 + repostIndicator: { 536 display: "flex", 537 + alignItems: "center", 538 + gap: 8, 539 fontSize: 13, 540 + fontWeight: 500, 541 + paddingLeft: 8, 542 + marginBottom: 4, 543 + } satisfies React.CSSProperties, 544 + repostAvatar: { 545 + width: 16, 546 + height: 16, 547 + borderRadius: "50%", 548 + objectFit: "cover", 549 } satisfies React.CSSProperties, 550 + repostText: { 551 + fontSize: 13, 552 fontWeight: 500, 553 } satisfies React.CSSProperties, 554 + replyIndicator: { 555 + display: "flex", 556 + alignItems: "center", 557 + gap: 8, 558 + fontSize: 13, 559 fontWeight: 500, 560 + paddingLeft: 8, 561 + marginBottom: 4, 562 + } satisfies React.CSSProperties, 563 + replyAvatar: { 564 + width: 16, 565 + height: 16, 566 + borderRadius: "50%", 567 + objectFit: "cover", 568 + } satisfies React.CSSProperties, 569 + replyText: { 570 + fontSize: 13, 571 + fontWeight: 500, 572 + } satisfies React.CSSProperties, 573 + postContent: { 574 + display: "flex", 575 + gap: 12, 576 + } satisfies React.CSSProperties, 577 + avatarContainer: { 578 + flexShrink: 0, 579 + } satisfies React.CSSProperties, 580 + avatar: { 581 + width: 48, 582 + height: 48, 583 + borderRadius: "50%", 584 + objectFit: "cover", 585 } satisfies React.CSSProperties, 586 + avatarPlaceholder: { 587 + width: 48, 588 + height: 48, 589 + borderRadius: "50%", 590 + background: "var(--atproto-color-bg-elevated)", 591 + color: "var(--atproto-color-text-secondary)", 592 + display: "flex", 593 + alignItems: "center", 594 + justifyContent: "center", 595 + fontSize: 18, 596 + fontWeight: 600, 597 + } satisfies React.CSSProperties, 598 + postMain: { 599 + flex: 1, 600 + minWidth: 0, 601 + display: "flex", 602 + flexDirection: "column", 603 + gap: 6, 604 + } satisfies React.CSSProperties, 605 + postHeader: { 606 + display: "flex", 607 + alignItems: "baseline", 608 + gap: 6, 609 + flexWrap: "wrap", 610 + } satisfies React.CSSProperties, 611 + authorName: { 612 + fontWeight: 700, 613 + fontSize: 15, 614 + textDecoration: "none", 615 + maxWidth: "200px", 616 + overflow: "hidden", 617 + textOverflow: "ellipsis", 618 + whiteSpace: "nowrap", 619 + } satisfies React.CSSProperties, 620 + authorHandle: { 621 + fontSize: 15, 622 + fontWeight: 400, 623 + maxWidth: "150px", 624 + overflow: "hidden", 625 + textOverflow: "ellipsis", 626 + whiteSpace: "nowrap", 627 + } satisfies React.CSSProperties, 628 + separator: { 629 + fontSize: 15, 630 + fontWeight: 400, 631 + } satisfies React.CSSProperties, 632 + timestamp: { 633 + fontSize: 15, 634 + fontWeight: 400, 635 + } satisfies React.CSSProperties, 636 + postLink: { 637 + textDecoration: "none", 638 + display: "block", 639 + } satisfies React.CSSProperties, 640 + postText: { 641 margin: 0, 642 whiteSpace: "pre-wrap", 643 + fontSize: 15, 644 + lineHeight: 1.5, 645 + wordBreak: "break-word", 646 } satisfies React.CSSProperties, 647 footer: { 648 display: "flex", ··· 651 padding: "12px 18px", 652 borderTop: "1px solid transparent", 653 fontSize: 13, 654 } satisfies React.CSSProperties, 655 pageChips: { 656 display: "flex", ··· 695 }; 696 697 export default BlueskyPostList;
+143
lib/components/CurrentlyPlaying.tsx
···
··· 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { CurrentlyPlayingRenderer } from "../renderers/CurrentlyPlayingRenderer"; 4 + import { useDidResolution } from "../hooks/useDidResolution"; 5 + import type { TealActorStatusRecord } from "../types/teal"; 6 + 7 + /** 8 + * Props for rendering teal.fm currently playing status. 9 + */ 10 + export interface CurrentlyPlayingProps { 11 + /** DID of the user whose currently playing status to display. */ 12 + did: string; 13 + /** Record key within the `fm.teal.alpha.actor.status` collection (usually "self"). */ 14 + rkey?: string; 15 + /** Prefetched teal.fm status record. When provided, skips fetching from the network. */ 16 + record?: TealActorStatusRecord; 17 + /** Optional renderer override for custom presentation. */ 18 + renderer?: React.ComponentType<CurrentlyPlayingRendererInjectedProps>; 19 + /** Fallback node displayed before loading begins. */ 20 + fallback?: React.ReactNode; 21 + /** Indicator node shown while data is loading. */ 22 + loadingIndicator?: React.ReactNode; 23 + /** Preferred color scheme for theming. */ 24 + colorScheme?: "light" | "dark" | "system"; 25 + /** Auto-refresh music data and album art. When true, refreshes every 15 seconds. Defaults to true. */ 26 + autoRefresh?: boolean; 27 + /** Refresh interval in milliseconds. Defaults to 15000 (15 seconds). Only used when autoRefresh is true. */ 28 + refreshInterval?: number; 29 + } 30 + 31 + /** 32 + * Values injected into custom currently playing renderer implementations. 33 + */ 34 + export type CurrentlyPlayingRendererInjectedProps = { 35 + /** Loaded teal.fm status record value. */ 36 + record: TealActorStatusRecord; 37 + /** Indicates whether the record is currently loading. */ 38 + loading: boolean; 39 + /** Fetch error, if any. */ 40 + error?: Error; 41 + /** Preferred color scheme for downstream components. */ 42 + colorScheme?: "light" | "dark" | "system"; 43 + /** DID associated with the record. */ 44 + did: string; 45 + /** Record key for the status. */ 46 + rkey: string; 47 + /** Label to display. */ 48 + label?: string; 49 + /** Handle to display in not listening state */ 50 + handle?: string; 51 + }; 52 + 53 + /** NSID for teal.fm actor status records. */ 54 + export const CURRENTLY_PLAYING_COLLECTION = "fm.teal.alpha.actor.status"; 55 + 56 + /** 57 + * Compares two teal.fm status records to determine if the track has changed. 58 + * Used to prevent unnecessary re-renders during auto-refresh when the same track is still playing. 59 + */ 60 + const compareTealRecords = ( 61 + prev: TealActorStatusRecord | undefined, 62 + next: TealActorStatusRecord | undefined 63 + ): boolean => { 64 + if (!prev || !next) return prev === next; 65 + 66 + const prevTrack = prev.item.trackName; 67 + const nextTrack = next.item.trackName; 68 + const prevArtist = prev.item.artists[0]?.artistName; 69 + const nextArtist = next.item.artists[0]?.artistName; 70 + 71 + return prevTrack === nextTrack && prevArtist === nextArtist; 72 + }; 73 + 74 + /** 75 + * Displays the currently playing track from teal.fm with auto-refresh. 76 + * 77 + * @param did - DID whose currently playing status should be fetched. 78 + * @param rkey - Record key within the teal.fm status collection (defaults to "self"). 79 + * @param renderer - Optional component override that will receive injected props. 80 + * @param fallback - Node rendered before the first load begins. 81 + * @param loadingIndicator - Node rendered while the status is loading. 82 + * @param colorScheme - Preferred color scheme for theming the renderer. 83 + * @param autoRefresh - When true (default), refreshes the record every 15 seconds (or custom interval). 84 + * @param refreshInterval - Custom refresh interval in milliseconds. Defaults to 15000 (15 seconds). 85 + * @returns A JSX subtree representing the currently playing track with loading states handled. 86 + */ 87 + export const CurrentlyPlaying: React.FC<CurrentlyPlayingProps> = React.memo(({ 88 + did, 89 + rkey = "self", 90 + record, 91 + renderer, 92 + fallback, 93 + loadingIndicator, 94 + colorScheme, 95 + autoRefresh = true, 96 + refreshInterval = 15000, 97 + }) => { 98 + // Resolve handle from DID 99 + const { handle } = useDidResolution(did); 100 + 101 + const Comp: React.ComponentType<CurrentlyPlayingRendererInjectedProps> = 102 + renderer ?? ((props) => <CurrentlyPlayingRenderer {...props} />); 103 + const Wrapped: React.FC<{ 104 + record: TealActorStatusRecord; 105 + loading: boolean; 106 + error?: Error; 107 + }> = (props) => ( 108 + <Comp 109 + {...props} 110 + colorScheme={colorScheme} 111 + did={did} 112 + rkey={rkey} 113 + label="CURRENTLY PLAYING" 114 + handle={handle} 115 + /> 116 + ); 117 + 118 + if (record !== undefined) { 119 + return ( 120 + <AtProtoRecord<TealActorStatusRecord> 121 + record={record} 122 + renderer={Wrapped} 123 + fallback={fallback} 124 + loadingIndicator={loadingIndicator} 125 + /> 126 + ); 127 + } 128 + 129 + return ( 130 + <AtProtoRecord<TealActorStatusRecord> 131 + did={did} 132 + collection={CURRENTLY_PLAYING_COLLECTION} 133 + rkey={rkey} 134 + renderer={Wrapped} 135 + fallback={fallback} 136 + loadingIndicator={loadingIndicator} 137 + refreshInterval={autoRefresh ? refreshInterval : undefined} 138 + compareRecords={compareTealRecords} 139 + /> 140 + ); 141 + }); 142 + 143 + export default CurrentlyPlaying;
+327
lib/components/GrainGallery.tsx
···
··· 1 + import React, { useMemo, useEffect, useState } from "react"; 2 + import { GrainGalleryRenderer, type GrainGalleryPhoto } from "../renderers/GrainGalleryRenderer"; 3 + import type { GrainGalleryRecord, GrainGalleryItemRecord, GrainPhotoRecord } from "../types/grain"; 4 + import type { ProfileRecord } from "../types/bluesky"; 5 + import { useDidResolution } from "../hooks/useDidResolution"; 6 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 7 + import { useBacklinks } from "../hooks/useBacklinks"; 8 + import { useBlob } from "../hooks/useBlob"; 9 + import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile"; 10 + import { getAvatarCid } from "../utils/profile"; 11 + import { formatDidForLabel, parseAtUri } from "../utils/at-uri"; 12 + import { isBlobWithCdn } from "../utils/blob"; 13 + import { createAtprotoClient } from "../utils/atproto-client"; 14 + 15 + /** 16 + * Props for rendering a grain.social gallery. 17 + */ 18 + export interface GrainGalleryProps { 19 + /** 20 + * Decentralized identifier for the repository that owns the gallery. 21 + */ 22 + did: string; 23 + /** 24 + * Record key identifying the specific gallery within the collection. 25 + */ 26 + rkey: string; 27 + /** 28 + * Prefetched gallery record. When provided, skips fetching the gallery from the network. 29 + */ 30 + record?: GrainGalleryRecord; 31 + /** 32 + * Custom renderer component that receives resolved gallery data and status flags. 33 + */ 34 + renderer?: React.ComponentType<GrainGalleryRendererInjectedProps>; 35 + /** 36 + * React node shown while the gallery query has not yet produced data or an error. 37 + */ 38 + fallback?: React.ReactNode; 39 + /** 40 + * React node displayed while the gallery fetch is actively loading. 41 + */ 42 + loadingIndicator?: React.ReactNode; 43 + /** 44 + * Constellation API base URL for fetching backlinks. 45 + */ 46 + constellationBaseUrl?: string; 47 + } 48 + 49 + /** 50 + * Values injected by `GrainGallery` into a downstream renderer component. 51 + */ 52 + export type GrainGalleryRendererInjectedProps = { 53 + /** 54 + * Resolved gallery record 55 + */ 56 + gallery: GrainGalleryRecord; 57 + /** 58 + * Array of photos in the gallery with their records and metadata 59 + */ 60 + photos: GrainGalleryPhoto[]; 61 + /** 62 + * `true` while network operations are in-flight. 63 + */ 64 + loading: boolean; 65 + /** 66 + * Error encountered during loading, if any. 67 + */ 68 + error?: Error; 69 + /** 70 + * The author's public handle derived from the DID. 71 + */ 72 + authorHandle?: string; 73 + /** 74 + * The author's display name from their profile. 75 + */ 76 + authorDisplayName?: string; 77 + /** 78 + * Resolved URL for the author's avatar blob, if available. 79 + */ 80 + avatarUrl?: string; 81 + }; 82 + 83 + export const GRAIN_GALLERY_COLLECTION = "social.grain.gallery"; 84 + export const GRAIN_GALLERY_ITEM_COLLECTION = "social.grain.gallery.item"; 85 + export const GRAIN_PHOTO_COLLECTION = "social.grain.photo"; 86 + 87 + /** 88 + * Fetches a grain.social gallery, resolves all photos via constellation backlinks, 89 + * and renders them in a grid layout. 90 + * 91 + * @param did - DID of the repository that stores the gallery. 92 + * @param rkey - Record key for the gallery. 93 + * @param record - Prefetched gallery record. 94 + * @param renderer - Optional renderer component to override the default. 95 + * @param fallback - Node rendered before the first fetch attempt resolves. 96 + * @param loadingIndicator - Node rendered while the gallery is loading. 97 + * @param constellationBaseUrl - Constellation API base URL. 98 + * @returns A component that renders loading/fallback states and the resolved gallery. 99 + */ 100 + export const GrainGallery: React.FC<GrainGalleryProps> = React.memo( 101 + ({ 102 + did: handleOrDid, 103 + rkey, 104 + record, 105 + renderer, 106 + fallback, 107 + loadingIndicator, 108 + constellationBaseUrl, 109 + }) => { 110 + const { 111 + did: resolvedDid, 112 + handle, 113 + loading: resolvingIdentity, 114 + error: resolutionError, 115 + } = useDidResolution(handleOrDid); 116 + 117 + const repoIdentifier = resolvedDid ?? handleOrDid; 118 + 119 + // Fetch author profile 120 + const { record: profile } = useAtProtoRecord<ProfileRecord>({ 121 + did: repoIdentifier, 122 + collection: BLUESKY_PROFILE_COLLECTION, 123 + rkey: "self", 124 + }); 125 + const avatar = profile?.avatar; 126 + const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined; 127 + const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(profile); 128 + const authorDisplayName = profile?.displayName; 129 + const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid); 130 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 131 + 132 + // Fetch gallery record 133 + const { 134 + record: fetchedGallery, 135 + loading: galleryLoading, 136 + error: galleryError, 137 + } = useAtProtoRecord<GrainGalleryRecord>({ 138 + did: record ? "" : repoIdentifier, 139 + collection: record ? "" : GRAIN_GALLERY_COLLECTION, 140 + rkey: record ? "" : rkey, 141 + }); 142 + 143 + const galleryRecord = record ?? fetchedGallery; 144 + const galleryUri = resolvedDid 145 + ? `at://${resolvedDid}/${GRAIN_GALLERY_COLLECTION}/${rkey}` 146 + : undefined; 147 + 148 + // Fetch backlinks to get gallery items 149 + const { 150 + backlinks, 151 + loading: backlinksLoading, 152 + error: backlinksError, 153 + } = useBacklinks({ 154 + subject: galleryUri || "", 155 + source: `${GRAIN_GALLERY_ITEM_COLLECTION}:gallery`, 156 + enabled: !!galleryUri && !!galleryRecord, 157 + constellationBaseUrl, 158 + }); 159 + 160 + // Fetch all gallery item records and photo records 161 + const [photos, setPhotos] = useState<GrainGalleryPhoto[]>([]); 162 + const [photosLoading, setPhotosLoading] = useState(false); 163 + const [photosError, setPhotosError] = useState<Error | undefined>(undefined); 164 + 165 + useEffect(() => { 166 + if (!backlinks || backlinks.length === 0) { 167 + setPhotos([]); 168 + return; 169 + } 170 + 171 + let cancelled = false; 172 + setPhotosLoading(true); 173 + setPhotosError(undefined); 174 + 175 + (async () => { 176 + try { 177 + const photoPromises = backlinks.map(async (backlink) => { 178 + // Create client for gallery item DID (uses slingshot + PDS fallback) 179 + const { rpc: galleryItemClient } = await createAtprotoClient({ 180 + did: backlink.did, 181 + }); 182 + 183 + // Fetch gallery item record 184 + const galleryItemRes = await ( 185 + galleryItemClient as unknown as { 186 + get: ( 187 + nsid: string, 188 + opts: { 189 + params: { 190 + repo: string; 191 + collection: string; 192 + rkey: string; 193 + }; 194 + }, 195 + ) => Promise<{ ok: boolean; data: { value: GrainGalleryItemRecord } }>; 196 + } 197 + ).get("com.atproto.repo.getRecord", { 198 + params: { 199 + repo: backlink.did, 200 + collection: GRAIN_GALLERY_ITEM_COLLECTION, 201 + rkey: backlink.rkey, 202 + }, 203 + }); 204 + 205 + if (!galleryItemRes.ok) return null; 206 + 207 + const galleryItem = galleryItemRes.data.value; 208 + 209 + // Parse photo URI 210 + const photoUri = parseAtUri(galleryItem.item); 211 + if (!photoUri) return null; 212 + 213 + // Create client for photo DID (uses slingshot + PDS fallback) 214 + const { rpc: photoClient } = await createAtprotoClient({ 215 + did: photoUri.did, 216 + }); 217 + 218 + // Fetch photo record 219 + const photoRes = await ( 220 + photoClient as unknown as { 221 + get: ( 222 + nsid: string, 223 + opts: { 224 + params: { 225 + repo: string; 226 + collection: string; 227 + rkey: string; 228 + }; 229 + }, 230 + ) => Promise<{ ok: boolean; data: { value: GrainPhotoRecord } }>; 231 + } 232 + ).get("com.atproto.repo.getRecord", { 233 + params: { 234 + repo: photoUri.did, 235 + collection: photoUri.collection, 236 + rkey: photoUri.rkey, 237 + }, 238 + }); 239 + 240 + if (!photoRes.ok) return null; 241 + 242 + const photoRecord = photoRes.data.value; 243 + 244 + return { 245 + record: photoRecord, 246 + did: photoUri.did, 247 + rkey: photoUri.rkey, 248 + position: galleryItem.position, 249 + } as GrainGalleryPhoto; 250 + }); 251 + 252 + const resolvedPhotos = await Promise.all(photoPromises); 253 + const validPhotos = resolvedPhotos.filter((p): p is NonNullable<typeof p> => p !== null) as GrainGalleryPhoto[]; 254 + 255 + if (!cancelled) { 256 + setPhotos(validPhotos); 257 + setPhotosLoading(false); 258 + } 259 + } catch (err) { 260 + if (!cancelled) { 261 + setPhotosError(err instanceof Error ? err : new Error("Failed to fetch photos")); 262 + setPhotosLoading(false); 263 + } 264 + } 265 + })(); 266 + 267 + return () => { 268 + cancelled = true; 269 + }; 270 + }, [backlinks]); 271 + 272 + const Comp: React.ComponentType<GrainGalleryRendererInjectedProps> = 273 + useMemo( 274 + () => 275 + renderer ?? ((props) => <GrainGalleryRenderer {...props} />), 276 + [renderer], 277 + ); 278 + 279 + const displayHandle = 280 + handle ?? 281 + (handleOrDid.startsWith("did:") ? undefined : handleOrDid); 282 + const authorHandle = 283 + displayHandle ?? formatDidForLabel(resolvedDid ?? handleOrDid); 284 + 285 + if (!displayHandle && resolvingIdentity) { 286 + return loadingIndicator || <div role="status" aria-live="polite" style={{ padding: 8 }}>Resolving handleโ€ฆ</div>; 287 + } 288 + if (!displayHandle && resolutionError) { 289 + return ( 290 + <div style={{ padding: 8, color: "crimson" }}> 291 + Could not resolve handle. 292 + </div> 293 + ); 294 + } 295 + 296 + if (galleryError || backlinksError || photosError) { 297 + return ( 298 + <div style={{ padding: 8, color: "crimson" }}> 299 + Failed to load gallery. 300 + </div> 301 + ); 302 + } 303 + 304 + if (!galleryRecord && galleryLoading) { 305 + return loadingIndicator || <div style={{ padding: 8 }}>Loading galleryโ€ฆ</div>; 306 + } 307 + 308 + if (!galleryRecord) { 309 + return fallback || <div style={{ padding: 8 }}>Gallery not found.</div>; 310 + } 311 + 312 + const loading = galleryLoading || backlinksLoading || photosLoading; 313 + 314 + return ( 315 + <Comp 316 + gallery={galleryRecord} 317 + photos={photos} 318 + loading={loading} 319 + authorHandle={authorHandle} 320 + authorDisplayName={authorDisplayName} 321 + avatarUrl={avatarUrl} 322 + /> 323 + ); 324 + }, 325 + ); 326 + 327 + export default GrainGallery;
+165
lib/components/LastPlayed.tsx
···
··· 1 + import React, { useMemo } from "react"; 2 + import { useLatestRecord } from "../hooks/useLatestRecord"; 3 + import { useDidResolution } from "../hooks/useDidResolution"; 4 + import { CurrentlyPlayingRenderer } from "../renderers/CurrentlyPlayingRenderer"; 5 + import type { TealFeedPlayRecord, TealActorStatusRecord } from "../types/teal"; 6 + 7 + /** 8 + * Props for rendering the last played track from teal.fm feed. 9 + */ 10 + export interface LastPlayedProps { 11 + /** DID of the user whose last played track to display. */ 12 + did: string; 13 + /** Optional renderer override for custom presentation. */ 14 + renderer?: React.ComponentType<LastPlayedRendererInjectedProps>; 15 + /** Fallback node displayed before loading begins. */ 16 + fallback?: React.ReactNode; 17 + /** Indicator node shown while data is loading. */ 18 + loadingIndicator?: React.ReactNode; 19 + /** Preferred color scheme for theming. */ 20 + colorScheme?: "light" | "dark" | "system"; 21 + /** Auto-refresh music data and album art. Defaults to false for last played. */ 22 + autoRefresh?: boolean; 23 + /** Refresh interval in milliseconds. Defaults to 60000 (60 seconds). */ 24 + refreshInterval?: number; 25 + } 26 + 27 + /** 28 + * Values injected into custom last played renderer implementations. 29 + */ 30 + export type LastPlayedRendererInjectedProps = { 31 + /** Loaded teal.fm feed play record value. */ 32 + record: TealActorStatusRecord; 33 + /** Indicates whether the record is currently loading. */ 34 + loading: boolean; 35 + /** Fetch error, if any. */ 36 + error?: Error; 37 + /** Preferred color scheme for downstream components. */ 38 + colorScheme?: "light" | "dark" | "system"; 39 + /** DID associated with the record. */ 40 + did: string; 41 + /** Record key for the play record. */ 42 + rkey: string; 43 + /** Handle to display in not listening state */ 44 + handle?: string; 45 + }; 46 + 47 + /** NSID for teal.fm feed play records. */ 48 + export const LAST_PLAYED_COLLECTION = "fm.teal.alpha.feed.play"; 49 + 50 + /** 51 + * Displays the last played track from teal.fm feed. 52 + * 53 + * @param did - DID whose last played track should be fetched. 54 + * @param renderer - Optional component override that will receive injected props. 55 + * @param fallback - Node rendered before the first load begins. 56 + * @param loadingIndicator - Node rendered while the data is loading. 57 + * @param colorScheme - Preferred color scheme for theming the renderer. 58 + * @param autoRefresh - When true, refreshes album art and streaming platform links at the specified interval. Defaults to false. 59 + * @param refreshInterval - Refresh interval in milliseconds. Defaults to 60000 (60 seconds). 60 + * @returns A JSX subtree representing the last played track with loading states handled. 61 + */ 62 + export const LastPlayed: React.FC<LastPlayedProps> = React.memo(({ 63 + did, 64 + renderer, 65 + fallback, 66 + loadingIndicator, 67 + colorScheme, 68 + autoRefresh = false, 69 + refreshInterval = 60000, 70 + }) => { 71 + // Resolve handle from DID 72 + const { handle } = useDidResolution(did); 73 + 74 + // Auto-refresh key for refetching teal.fm record 75 + const [refreshKey, setRefreshKey] = React.useState(0); 76 + 77 + // Auto-refresh interval 78 + React.useEffect(() => { 79 + if (!autoRefresh) return; 80 + 81 + const interval = setInterval(() => { 82 + setRefreshKey((prev) => prev + 1); 83 + }, refreshInterval); 84 + 85 + return () => clearInterval(interval); 86 + }, [autoRefresh, refreshInterval]); 87 + 88 + const { record, rkey, loading, error, empty } = useLatestRecord<TealFeedPlayRecord>( 89 + did, 90 + LAST_PLAYED_COLLECTION, 91 + refreshKey, 92 + ); 93 + 94 + // Normalize TealFeedPlayRecord to match TealActorStatusRecord structure 95 + // Use useMemo to prevent creating new object on every render 96 + // MUST be called before any conditional returns (Rules of Hooks) 97 + const normalizedRecord = useMemo(() => { 98 + if (!record) return null; 99 + 100 + return { 101 + $type: "fm.teal.alpha.actor.status" as const, 102 + item: { 103 + artists: record.artists, 104 + originUrl: record.originUrl, 105 + trackName: record.trackName, 106 + playedTime: record.playedTime, 107 + releaseName: record.releaseName, 108 + recordingMbId: record.recordingMbId, 109 + releaseMbId: record.releaseMbId, 110 + submissionClientAgent: record.submissionClientAgent, 111 + musicServiceBaseDomain: record.musicServiceBaseDomain, 112 + isrc: record.isrc, 113 + duration: record.duration, 114 + }, 115 + time: new Date(record.playedTime).getTime().toString(), 116 + expiry: undefined, 117 + }; 118 + }, [record]); 119 + 120 + const Comp = renderer ?? CurrentlyPlayingRenderer; 121 + 122 + // Now handle conditional returns after all hooks 123 + if (error) { 124 + return ( 125 + <div style={{ padding: 8, color: "var(--atproto-color-error)" }}> 126 + Failed to load last played track. 127 + </div> 128 + ); 129 + } 130 + 131 + if (loading && !record) { 132 + return loadingIndicator ? ( 133 + <>{loadingIndicator}</> 134 + ) : ( 135 + <div style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 136 + Loadingโ€ฆ 137 + </div> 138 + ); 139 + } 140 + 141 + if (empty || !record || !normalizedRecord) { 142 + return fallback ? ( 143 + <>{fallback}</> 144 + ) : ( 145 + <div style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 146 + No plays found. 147 + </div> 148 + ); 149 + } 150 + 151 + return ( 152 + <Comp 153 + record={normalizedRecord} 154 + loading={loading} 155 + error={error} 156 + colorScheme={colorScheme} 157 + did={did} 158 + rkey={rkey || "unknown"} 159 + label="LAST PLAYED" 160 + handle={handle} 161 + /> 162 + ); 163 + }); 164 + 165 + export default LastPlayed;
+7 -4
lib/components/RichText.tsx
··· 1 import React from "react"; 2 import type { AppBskyRichtextFacet } from "@atcute/bluesky"; 3 import { createTextSegments, type TextSegment } from "../utils/richtext"; 4 5 export interface RichTextProps { 6 text: string; ··· 13 * Properly handles byte offsets and multi-byte characters. 14 */ 15 export const RichText: React.FC<RichTextProps> = ({ text, facets, style }) => { 16 const segments = createTextSegments(text, facets); 17 18 return ( 19 <span style={style}> 20 {segments.map((segment, idx) => ( 21 - <RichTextSegment key={idx} segment={segment} /> 22 ))} 23 </span> 24 ); ··· 26 27 interface RichTextSegmentProps { 28 segment: TextSegment; 29 } 30 31 - const RichTextSegment: React.FC<RichTextSegmentProps> = ({ segment }) => { 32 if (!segment.facet) { 33 return <>{segment.text}</>; 34 } ··· 68 69 case "app.bsky.richtext.facet#mention": { 70 const mentionFeature = feature as AppBskyRichtextFacet.Mention; 71 - const profileUrl = `https://bsky.app/profile/${mentionFeature.did}`; 72 return ( 73 <a 74 href={profileUrl} ··· 92 93 case "app.bsky.richtext.facet#tag": { 94 const tagFeature = feature as AppBskyRichtextFacet.Tag; 95 - const tagUrl = `https://bsky.app/hashtag/${encodeURIComponent(tagFeature.tag)}`; 96 return ( 97 <a 98 href={tagUrl}
··· 1 import React from "react"; 2 import type { AppBskyRichtextFacet } from "@atcute/bluesky"; 3 import { createTextSegments, type TextSegment } from "../utils/richtext"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 6 export interface RichTextProps { 7 text: string; ··· 14 * Properly handles byte offsets and multi-byte characters. 15 */ 16 export const RichText: React.FC<RichTextProps> = ({ text, facets, style }) => { 17 + const { blueskyAppBaseUrl } = useAtProto(); 18 const segments = createTextSegments(text, facets); 19 20 return ( 21 <span style={style}> 22 {segments.map((segment, idx) => ( 23 + <RichTextSegment key={idx} segment={segment} blueskyAppBaseUrl={blueskyAppBaseUrl} /> 24 ))} 25 </span> 26 ); ··· 28 29 interface RichTextSegmentProps { 30 segment: TextSegment; 31 + blueskyAppBaseUrl: string; 32 } 33 34 + const RichTextSegment: React.FC<RichTextSegmentProps> = ({ segment, blueskyAppBaseUrl }) => { 35 if (!segment.facet) { 36 return <>{segment.text}</>; 37 } ··· 71 72 case "app.bsky.richtext.facet#mention": { 73 const mentionFeature = feature as AppBskyRichtextFacet.Mention; 74 + const profileUrl = `${blueskyAppBaseUrl}/profile/${mentionFeature.did}`; 75 return ( 76 <a 77 href={profileUrl} ··· 95 96 case "app.bsky.richtext.facet#tag": { 97 const tagFeature = feature as AppBskyRichtextFacet.Tag; 98 + const tagUrl = `${blueskyAppBaseUrl}/hashtag/${encodeURIComponent(tagFeature.tag)}`; 99 return ( 100 <a 101 href={tagUrl}
+648
lib/components/SongHistoryList.tsx
···
··· 1 + import React, { useState, useEffect, useMemo } from "react"; 2 + import { usePaginatedRecords } from "../hooks/usePaginatedRecords"; 3 + import { useDidResolution } from "../hooks/useDidResolution"; 4 + import type { TealFeedPlayRecord } from "../types/teal"; 5 + 6 + /** 7 + * Options for rendering a paginated list of song history from teal.fm. 8 + */ 9 + export interface SongHistoryListProps { 10 + /** 11 + * DID whose song history should be fetched. 12 + */ 13 + did: string; 14 + /** 15 + * Maximum number of records to list per page. Defaults to `6`. 16 + */ 17 + limit?: number; 18 + /** 19 + * Enables pagination controls when `true`. Defaults to `true`. 20 + */ 21 + enablePagination?: boolean; 22 + } 23 + 24 + interface SonglinkResponse { 25 + linksByPlatform: { 26 + [platform: string]: { 27 + url: string; 28 + entityUniqueId: string; 29 + }; 30 + }; 31 + entitiesByUniqueId: { 32 + [id: string]: { 33 + thumbnailUrl?: string; 34 + title?: string; 35 + artistName?: string; 36 + }; 37 + }; 38 + entityUniqueId?: string; 39 + } 40 + 41 + /** 42 + * Fetches a user's song history from teal.fm and renders them with album art focus. 43 + * 44 + * @param did - DID whose song history should be displayed. 45 + * @param limit - Maximum number of songs per page. Default `6`. 46 + * @param enablePagination - Whether pagination controls should render. Default `true`. 47 + * @returns A card-like list element with loading, empty, and error handling. 48 + */ 49 + export const SongHistoryList: React.FC<SongHistoryListProps> = React.memo(({ 50 + did, 51 + limit = 6, 52 + enablePagination = true, 53 + }) => { 54 + const { handle: resolvedHandle } = useDidResolution(did); 55 + const actorLabel = resolvedHandle ?? formatDid(did); 56 + 57 + const { 58 + records, 59 + loading, 60 + error, 61 + hasNext, 62 + hasPrev, 63 + loadNext, 64 + loadPrev, 65 + pageIndex, 66 + pagesCount, 67 + } = usePaginatedRecords<TealFeedPlayRecord>({ 68 + did, 69 + collection: "fm.teal.alpha.feed.play", 70 + limit, 71 + }); 72 + 73 + const pageLabel = useMemo(() => { 74 + const knownTotal = Math.max(pageIndex + 1, pagesCount); 75 + if (!enablePagination) return undefined; 76 + if (hasNext && knownTotal === pageIndex + 1) 77 + return `${pageIndex + 1}/โ€ฆ`; 78 + return `${pageIndex + 1}/${knownTotal}`; 79 + }, [enablePagination, hasNext, pageIndex, pagesCount]); 80 + 81 + if (error) 82 + return ( 83 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 84 + Failed to load song history. 85 + </div> 86 + ); 87 + 88 + return ( 89 + <div style={{ ...listStyles.card, background: `var(--atproto-color-bg)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)` }}> 90 + <div style={{ ...listStyles.header, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text)` }}> 91 + <div style={listStyles.headerInfo}> 92 + <div style={listStyles.headerIcon}> 93 + <svg 94 + width="24" 95 + height="24" 96 + viewBox="0 0 24 24" 97 + fill="none" 98 + stroke="currentColor" 99 + strokeWidth="2" 100 + strokeLinecap="round" 101 + strokeLinejoin="round" 102 + > 103 + <path d="M9 18V5l12-2v13" /> 104 + <circle cx="6" cy="18" r="3" /> 105 + <circle cx="18" cy="16" r="3" /> 106 + </svg> 107 + </div> 108 + <div style={listStyles.headerText}> 109 + <span style={listStyles.title}>Listening History</span> 110 + <span 111 + style={{ 112 + ...listStyles.subtitle, 113 + color: `var(--atproto-color-text-secondary)`, 114 + }} 115 + > 116 + @{actorLabel} 117 + </span> 118 + </div> 119 + </div> 120 + {pageLabel && ( 121 + <span 122 + style={{ ...listStyles.pageMeta, color: `var(--atproto-color-text-secondary)` }} 123 + > 124 + {pageLabel} 125 + </span> 126 + )} 127 + </div> 128 + <div style={listStyles.items}> 129 + {loading && records.length === 0 && ( 130 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 131 + Loading songsโ€ฆ 132 + </div> 133 + )} 134 + {records.map((record, idx) => ( 135 + <SongRow 136 + key={`${record.rkey}-${record.value.playedTime}`} 137 + record={record.value} 138 + hasDivider={idx < records.length - 1} 139 + /> 140 + ))} 141 + {!loading && records.length === 0 && ( 142 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 143 + No songs found. 144 + </div> 145 + )} 146 + </div> 147 + {enablePagination && ( 148 + <div style={{ ...listStyles.footer, borderTopColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 149 + <button 150 + type="button" 151 + style={{ 152 + ...listStyles.pageButton, 153 + background: `var(--atproto-color-button-bg)`, 154 + color: `var(--atproto-color-button-text)`, 155 + cursor: hasPrev ? "pointer" : "not-allowed", 156 + opacity: hasPrev ? 1 : 0.5, 157 + }} 158 + onClick={loadPrev} 159 + disabled={!hasPrev} 160 + > 161 + โ€น Prev 162 + </button> 163 + <div style={listStyles.pageChips}> 164 + <span 165 + style={{ 166 + ...listStyles.pageChipActive, 167 + color: `var(--atproto-color-button-text)`, 168 + background: `var(--atproto-color-button-bg)`, 169 + borderWidth: "1px", 170 + borderStyle: "solid", 171 + borderColor: `var(--atproto-color-button-bg)`, 172 + }} 173 + > 174 + {pageIndex + 1} 175 + </span> 176 + {(hasNext || pagesCount > pageIndex + 1) && ( 177 + <span 178 + style={{ 179 + ...listStyles.pageChip, 180 + color: `var(--atproto-color-text-secondary)`, 181 + borderWidth: "1px", 182 + borderStyle: "solid", 183 + borderColor: `var(--atproto-color-border)`, 184 + background: `var(--atproto-color-bg)`, 185 + }} 186 + > 187 + {pageIndex + 2} 188 + </span> 189 + )} 190 + </div> 191 + <button 192 + type="button" 193 + style={{ 194 + ...listStyles.pageButton, 195 + background: `var(--atproto-color-button-bg)`, 196 + color: `var(--atproto-color-button-text)`, 197 + cursor: hasNext ? "pointer" : "not-allowed", 198 + opacity: hasNext ? 1 : 0.5, 199 + }} 200 + onClick={loadNext} 201 + disabled={!hasNext} 202 + > 203 + Next โ€บ 204 + </button> 205 + </div> 206 + )} 207 + {loading && records.length > 0 && ( 208 + <div 209 + style={{ ...listStyles.loadingBar, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text-secondary)` }} 210 + > 211 + Updatingโ€ฆ 212 + </div> 213 + )} 214 + </div> 215 + ); 216 + }); 217 + 218 + interface SongRowProps { 219 + record: TealFeedPlayRecord; 220 + hasDivider: boolean; 221 + } 222 + 223 + const SongRow: React.FC<SongRowProps> = ({ record, hasDivider }) => { 224 + const [albumArt, setAlbumArt] = useState<string | undefined>(undefined); 225 + const [artLoading, setArtLoading] = useState(true); 226 + 227 + const artistNames = record.artists.map((a) => a.artistName).join(", "); 228 + const relative = record.playedTime 229 + ? formatRelativeTime(record.playedTime) 230 + : undefined; 231 + const absolute = record.playedTime 232 + ? new Date(record.playedTime).toLocaleString() 233 + : undefined; 234 + 235 + useEffect(() => { 236 + let cancelled = false; 237 + setArtLoading(true); 238 + setAlbumArt(undefined); 239 + 240 + const fetchAlbumArt = async () => { 241 + try { 242 + // Try ISRC first 243 + if (record.isrc) { 244 + const response = await fetch( 245 + `https://api.song.link/v1-alpha.1/links?platform=isrc&type=song&id=${encodeURIComponent(record.isrc)}&songIfSingle=true` 246 + ); 247 + if (cancelled) return; 248 + if (response.ok) { 249 + const data: SonglinkResponse = await response.json(); 250 + const entityId = data.entityUniqueId; 251 + const entity = entityId ? data.entitiesByUniqueId?.[entityId] : undefined; 252 + if (entity?.thumbnailUrl) { 253 + setAlbumArt(entity.thumbnailUrl); 254 + setArtLoading(false); 255 + return; 256 + } 257 + } 258 + } 259 + 260 + // Fallback to iTunes search 261 + const iTunesSearchUrl = `https://itunes.apple.com/search?term=${encodeURIComponent( 262 + `${record.trackName} ${artistNames}` 263 + )}&media=music&entity=song&limit=1`; 264 + 265 + const iTunesResponse = await fetch(iTunesSearchUrl); 266 + if (cancelled) return; 267 + 268 + if (iTunesResponse.ok) { 269 + const iTunesData = await iTunesResponse.json(); 270 + if (iTunesData.results && iTunesData.results.length > 0) { 271 + const match = iTunesData.results[0]; 272 + const artworkUrl = match.artworkUrl100?.replace('100x100', '600x600') || match.artworkUrl100; 273 + if (artworkUrl) { 274 + setAlbumArt(artworkUrl); 275 + } 276 + } 277 + } 278 + setArtLoading(false); 279 + } catch (err) { 280 + console.error(`Failed to fetch album art for "${record.trackName}":`, err); 281 + setArtLoading(false); 282 + } 283 + }; 284 + 285 + fetchAlbumArt(); 286 + 287 + return () => { 288 + cancelled = true; 289 + }; 290 + }, [record.trackName, artistNames, record.isrc]); 291 + 292 + return ( 293 + <div 294 + style={{ 295 + ...listStyles.row, 296 + color: `var(--atproto-color-text)`, 297 + borderBottom: hasDivider 298 + ? `1px solid var(--atproto-color-border)` 299 + : "none", 300 + }} 301 + > 302 + {/* Album Art - Large and prominent */} 303 + <div style={listStyles.albumArtContainer}> 304 + {artLoading ? ( 305 + <div style={listStyles.albumArtPlaceholder}> 306 + <div style={listStyles.loadingSpinner} /> 307 + </div> 308 + ) : albumArt ? ( 309 + <img 310 + src={albumArt} 311 + alt={`${record.releaseName || "Album"} cover`} 312 + style={listStyles.albumArt} 313 + onError={(e) => { 314 + e.currentTarget.style.display = "none"; 315 + const parent = e.currentTarget.parentElement; 316 + if (parent) { 317 + const placeholder = document.createElement("div"); 318 + Object.assign(placeholder.style, listStyles.albumArtPlaceholder); 319 + placeholder.innerHTML = ` 320 + <svg 321 + width="48" 322 + height="48" 323 + viewBox="0 0 24 24" 324 + fill="none" 325 + stroke="currentColor" 326 + stroke-width="1.5" 327 + > 328 + <circle cx="12" cy="12" r="10" /> 329 + <circle cx="12" cy="12" r="3" /> 330 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 331 + </svg> 332 + `; 333 + parent.appendChild(placeholder); 334 + } 335 + }} 336 + /> 337 + ) : ( 338 + <div style={listStyles.albumArtPlaceholder}> 339 + <svg 340 + width="48" 341 + height="48" 342 + viewBox="0 0 24 24" 343 + fill="none" 344 + stroke="currentColor" 345 + strokeWidth="1.5" 346 + > 347 + <circle cx="12" cy="12" r="10" /> 348 + <circle cx="12" cy="12" r="3" /> 349 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 350 + </svg> 351 + </div> 352 + )} 353 + </div> 354 + 355 + {/* Song Info */} 356 + <div style={listStyles.songInfo}> 357 + <div style={listStyles.trackName}>{record.trackName}</div> 358 + <div style={{ ...listStyles.artistName, color: `var(--atproto-color-text-secondary)` }}> 359 + {artistNames} 360 + </div> 361 + {record.releaseName && ( 362 + <div style={{ ...listStyles.releaseName, color: `var(--atproto-color-text-secondary)` }}> 363 + {record.releaseName} 364 + </div> 365 + )} 366 + {relative && ( 367 + <div 368 + style={{ ...listStyles.playedTime, color: `var(--atproto-color-text-secondary)` }} 369 + title={absolute} 370 + > 371 + {relative} 372 + </div> 373 + )} 374 + </div> 375 + 376 + {/* External Link */} 377 + {record.originUrl && ( 378 + <a 379 + href={record.originUrl} 380 + target="_blank" 381 + rel="noopener noreferrer" 382 + style={listStyles.externalLink} 383 + title="Listen on streaming service" 384 + aria-label={`Listen to ${record.trackName} by ${artistNames}`} 385 + > 386 + <svg 387 + width="20" 388 + height="20" 389 + viewBox="0 0 24 24" 390 + fill="none" 391 + stroke="currentColor" 392 + strokeWidth="2" 393 + strokeLinecap="round" 394 + strokeLinejoin="round" 395 + > 396 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 397 + <polyline points="15 3 21 3 21 9" /> 398 + <line x1="10" y1="14" x2="21" y2="3" /> 399 + </svg> 400 + </a> 401 + )} 402 + </div> 403 + ); 404 + }; 405 + 406 + function formatDid(did: string) { 407 + return did.replace(/^did:(plc:)?/, ""); 408 + } 409 + 410 + function formatRelativeTime(iso: string): string { 411 + const date = new Date(iso); 412 + const diffSeconds = (date.getTime() - Date.now()) / 1000; 413 + const absSeconds = Math.abs(diffSeconds); 414 + const thresholds: Array<{ 415 + limit: number; 416 + unit: Intl.RelativeTimeFormatUnit; 417 + divisor: number; 418 + }> = [ 419 + { limit: 60, unit: "second", divisor: 1 }, 420 + { limit: 3600, unit: "minute", divisor: 60 }, 421 + { limit: 86400, unit: "hour", divisor: 3600 }, 422 + { limit: 604800, unit: "day", divisor: 86400 }, 423 + { limit: 2629800, unit: "week", divisor: 604800 }, 424 + { limit: 31557600, unit: "month", divisor: 2629800 }, 425 + { limit: Infinity, unit: "year", divisor: 31557600 }, 426 + ]; 427 + const threshold = 428 + thresholds.find((t) => absSeconds < t.limit) ?? 429 + thresholds[thresholds.length - 1]; 430 + const value = diffSeconds / threshold.divisor; 431 + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 432 + return rtf.format(Math.round(value), threshold.unit); 433 + } 434 + 435 + const listStyles = { 436 + card: { 437 + borderRadius: 16, 438 + borderWidth: "1px", 439 + borderStyle: "solid", 440 + borderColor: "transparent", 441 + boxShadow: "0 8px 18px -12px rgba(15, 23, 42, 0.25)", 442 + overflow: "hidden", 443 + display: "flex", 444 + flexDirection: "column", 445 + } satisfies React.CSSProperties, 446 + header: { 447 + display: "flex", 448 + alignItems: "center", 449 + justifyContent: "space-between", 450 + padding: "14px 18px", 451 + fontSize: 14, 452 + fontWeight: 500, 453 + borderBottom: "1px solid var(--atproto-color-border)", 454 + } satisfies React.CSSProperties, 455 + headerInfo: { 456 + display: "flex", 457 + alignItems: "center", 458 + gap: 12, 459 + } satisfies React.CSSProperties, 460 + headerIcon: { 461 + width: 28, 462 + height: 28, 463 + display: "flex", 464 + alignItems: "center", 465 + justifyContent: "center", 466 + borderRadius: "50%", 467 + color: "var(--atproto-color-text)", 468 + } satisfies React.CSSProperties, 469 + headerText: { 470 + display: "flex", 471 + flexDirection: "column", 472 + gap: 2, 473 + } satisfies React.CSSProperties, 474 + title: { 475 + fontSize: 15, 476 + fontWeight: 600, 477 + } satisfies React.CSSProperties, 478 + subtitle: { 479 + fontSize: 12, 480 + fontWeight: 500, 481 + } satisfies React.CSSProperties, 482 + pageMeta: { 483 + fontSize: 12, 484 + } satisfies React.CSSProperties, 485 + items: { 486 + display: "flex", 487 + flexDirection: "column", 488 + } satisfies React.CSSProperties, 489 + empty: { 490 + padding: "24px 18px", 491 + fontSize: 13, 492 + textAlign: "center", 493 + } satisfies React.CSSProperties, 494 + row: { 495 + padding: "18px", 496 + display: "flex", 497 + gap: 16, 498 + alignItems: "center", 499 + transition: "background-color 120ms ease", 500 + position: "relative", 501 + } satisfies React.CSSProperties, 502 + albumArtContainer: { 503 + width: 96, 504 + height: 96, 505 + flexShrink: 0, 506 + borderRadius: 8, 507 + overflow: "hidden", 508 + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", 509 + } satisfies React.CSSProperties, 510 + albumArt: { 511 + width: "100%", 512 + height: "100%", 513 + objectFit: "cover", 514 + display: "block", 515 + } satisfies React.CSSProperties, 516 + albumArtPlaceholder: { 517 + width: "100%", 518 + height: "100%", 519 + display: "flex", 520 + alignItems: "center", 521 + justifyContent: "center", 522 + background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", 523 + color: "rgba(255, 255, 255, 0.6)", 524 + } satisfies React.CSSProperties, 525 + loadingSpinner: { 526 + width: 28, 527 + height: 28, 528 + border: "3px solid rgba(255, 255, 255, 0.3)", 529 + borderTop: "3px solid rgba(255, 255, 255, 0.9)", 530 + borderRadius: "50%", 531 + animation: "spin 1s linear infinite", 532 + } satisfies React.CSSProperties, 533 + songInfo: { 534 + flex: 1, 535 + display: "flex", 536 + flexDirection: "column", 537 + gap: 4, 538 + minWidth: 0, 539 + } satisfies React.CSSProperties, 540 + trackName: { 541 + fontSize: 16, 542 + fontWeight: 600, 543 + lineHeight: 1.3, 544 + color: "var(--atproto-color-text)", 545 + overflow: "hidden", 546 + textOverflow: "ellipsis", 547 + whiteSpace: "nowrap", 548 + } satisfies React.CSSProperties, 549 + artistName: { 550 + fontSize: 14, 551 + fontWeight: 500, 552 + overflow: "hidden", 553 + textOverflow: "ellipsis", 554 + whiteSpace: "nowrap", 555 + } satisfies React.CSSProperties, 556 + releaseName: { 557 + fontSize: 13, 558 + overflow: "hidden", 559 + textOverflow: "ellipsis", 560 + whiteSpace: "nowrap", 561 + } satisfies React.CSSProperties, 562 + playedTime: { 563 + fontSize: 12, 564 + fontWeight: 500, 565 + marginTop: 2, 566 + } satisfies React.CSSProperties, 567 + externalLink: { 568 + flexShrink: 0, 569 + width: 36, 570 + height: 36, 571 + display: "flex", 572 + alignItems: "center", 573 + justifyContent: "center", 574 + borderRadius: "50%", 575 + background: "var(--atproto-color-bg-elevated)", 576 + border: "1px solid var(--atproto-color-border)", 577 + color: "var(--atproto-color-text-secondary)", 578 + cursor: "pointer", 579 + transition: "all 0.2s ease", 580 + textDecoration: "none", 581 + } satisfies React.CSSProperties, 582 + footer: { 583 + display: "flex", 584 + alignItems: "center", 585 + justifyContent: "space-between", 586 + padding: "12px 18px", 587 + borderTop: "1px solid transparent", 588 + fontSize: 13, 589 + } satisfies React.CSSProperties, 590 + pageChips: { 591 + display: "flex", 592 + gap: 6, 593 + alignItems: "center", 594 + } satisfies React.CSSProperties, 595 + pageChip: { 596 + padding: "4px 10px", 597 + borderRadius: 999, 598 + fontSize: 13, 599 + borderWidth: "1px", 600 + borderStyle: "solid", 601 + borderColor: "transparent", 602 + } satisfies React.CSSProperties, 603 + pageChipActive: { 604 + padding: "4px 10px", 605 + borderRadius: 999, 606 + fontSize: 13, 607 + fontWeight: 600, 608 + borderWidth: "1px", 609 + borderStyle: "solid", 610 + borderColor: "transparent", 611 + } satisfies React.CSSProperties, 612 + pageButton: { 613 + border: "none", 614 + borderRadius: 999, 615 + padding: "6px 12px", 616 + fontSize: 13, 617 + fontWeight: 500, 618 + background: "transparent", 619 + display: "flex", 620 + alignItems: "center", 621 + gap: 4, 622 + transition: "background-color 120ms ease", 623 + } satisfies React.CSSProperties, 624 + loadingBar: { 625 + padding: "4px 18px 14px", 626 + fontSize: 12, 627 + textAlign: "right", 628 + color: "#64748b", 629 + } satisfies React.CSSProperties, 630 + }; 631 + 632 + // Add keyframes and hover styles 633 + if (typeof document !== "undefined") { 634 + const styleId = "song-history-styles"; 635 + if (!document.getElementById(styleId)) { 636 + const styleElement = document.createElement("style"); 637 + styleElement.id = styleId; 638 + styleElement.textContent = ` 639 + @keyframes spin { 640 + 0% { transform: rotate(0deg); } 641 + 100% { transform: rotate(360deg); } 642 + } 643 + `; 644 + document.head.appendChild(styleElement); 645 + } 646 + } 647 + 648 + export default SongHistoryList;
+131
lib/components/TangledRepo.tsx
···
··· 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { TangledRepoRenderer } from "../renderers/TangledRepoRenderer"; 4 + import type { TangledRepoRecord } from "../types/tangled"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 + 7 + /** 8 + * Props for rendering Tangled Repo records. 9 + */ 10 + export interface TangledRepoProps { 11 + /** DID of the repository that stores the repo record. */ 12 + did: string; 13 + /** Record key within the `sh.tangled.repo` collection. */ 14 + rkey: string; 15 + /** Prefetched Tangled Repo record. When provided, skips fetching from the network. */ 16 + record?: TangledRepoRecord; 17 + /** Optional renderer override for custom presentation. */ 18 + renderer?: React.ComponentType<TangledRepoRendererInjectedProps>; 19 + /** Fallback node displayed before loading begins. */ 20 + fallback?: React.ReactNode; 21 + /** Indicator node shown while data is loading. */ 22 + loadingIndicator?: React.ReactNode; 23 + /** Preferred color scheme for theming. */ 24 + colorScheme?: "light" | "dark" | "system"; 25 + /** Whether to show star count from backlinks. Defaults to true. */ 26 + showStarCount?: boolean; 27 + /** Branch to query for language information. Defaults to trying "main", then "master". */ 28 + branch?: string; 29 + /** Prefetched language names (e.g., ['TypeScript', 'React']). When provided, skips fetching languages from the knot server. */ 30 + languages?: string[]; 31 + } 32 + 33 + /** 34 + * Values injected into custom Tangled Repo renderer implementations. 35 + */ 36 + export type TangledRepoRendererInjectedProps = { 37 + /** Loaded Tangled Repo record value. */ 38 + record: TangledRepoRecord; 39 + /** Indicates whether the record is currently loading. */ 40 + loading: boolean; 41 + /** Fetch error, if any. */ 42 + error?: Error; 43 + /** Preferred color scheme for downstream components. */ 44 + colorScheme?: "light" | "dark" | "system"; 45 + /** DID associated with the record. */ 46 + did: string; 47 + /** Record key for the repo. */ 48 + rkey: string; 49 + /** Canonical external URL for linking to the repo. */ 50 + canonicalUrl: string; 51 + /** Whether to show star count from backlinks. */ 52 + showStarCount?: boolean; 53 + /** Branch to query for language information. */ 54 + branch?: string; 55 + /** Prefetched language names. */ 56 + languages?: string[]; 57 + }; 58 + 59 + /** NSID for Tangled Repo records. */ 60 + export const TANGLED_REPO_COLLECTION = "sh.tangled.repo"; 61 + 62 + /** 63 + * Resolves a Tangled Repo record and renders it with optional overrides while computing a canonical link. 64 + * 65 + * @param did - DID whose Tangled Repo should be fetched. 66 + * @param rkey - Record key within the Tangled Repo collection. 67 + * @param renderer - Optional component override that will receive injected props. 68 + * @param fallback - Node rendered before the first load begins. 69 + * @param loadingIndicator - Node rendered while the Tangled Repo is loading. 70 + * @param colorScheme - Preferred color scheme for theming the renderer. 71 + * @param showStarCount - Whether to show star count from backlinks. Defaults to true. 72 + * @param branch - Branch to query for language information. Defaults to trying "main", then "master". 73 + * @param languages - Prefetched language names (e.g., ['TypeScript', 'React']). When provided, skips fetching languages from the knot server. 74 + * @returns A JSX subtree representing the Tangled Repo record with loading states handled. 75 + */ 76 + export const TangledRepo: React.FC<TangledRepoProps> = React.memo(({ 77 + did, 78 + rkey, 79 + record, 80 + renderer, 81 + fallback, 82 + loadingIndicator, 83 + colorScheme, 84 + showStarCount = true, 85 + branch, 86 + languages, 87 + }) => { 88 + const { tangledBaseUrl } = useAtProto(); 89 + const Comp: React.ComponentType<TangledRepoRendererInjectedProps> = 90 + renderer ?? ((props) => <TangledRepoRenderer {...props} />); 91 + const Wrapped: React.FC<{ 92 + record: TangledRepoRecord; 93 + loading: boolean; 94 + error?: Error; 95 + }> = (props) => ( 96 + <Comp 97 + {...props} 98 + colorScheme={colorScheme} 99 + did={did} 100 + rkey={rkey} 101 + canonicalUrl={`${tangledBaseUrl}/${did}/${encodeURIComponent(props.record.name)}`} 102 + showStarCount={showStarCount} 103 + branch={branch} 104 + languages={languages} 105 + /> 106 + ); 107 + 108 + if (record !== undefined) { 109 + return ( 110 + <AtProtoRecord<TangledRepoRecord> 111 + record={record} 112 + renderer={Wrapped} 113 + fallback={fallback} 114 + loadingIndicator={loadingIndicator} 115 + /> 116 + ); 117 + } 118 + 119 + return ( 120 + <AtProtoRecord<TangledRepoRecord> 121 + did={did} 122 + collection={TANGLED_REPO_COLLECTION} 123 + rkey={rkey} 124 + renderer={Wrapped} 125 + fallback={fallback} 126 + loadingIndicator={loadingIndicator} 127 + /> 128 + ); 129 + }); 130 + 131 + export default TangledRepo;
+4 -2
lib/components/TangledString.tsx
··· 1 import React from "react"; 2 import { AtProtoRecord } from "../core/AtProtoRecord"; 3 import { TangledStringRenderer } from "../renderers/TangledStringRenderer"; 4 - import type { TangledStringRecord } from "../renderers/TangledStringRenderer"; 5 6 /** 7 * Props for rendering Tangled String records. ··· 66 loadingIndicator, 67 colorScheme, 68 }) => { 69 const Comp: React.ComponentType<TangledStringRendererInjectedProps> = 70 renderer ?? ((props) => <TangledStringRenderer {...props} />); 71 const Wrapped: React.FC<{ ··· 78 colorScheme={colorScheme} 79 did={did} 80 rkey={rkey} 81 - canonicalUrl={`https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`} 82 /> 83 ); 84
··· 1 import React from "react"; 2 import { AtProtoRecord } from "../core/AtProtoRecord"; 3 import { TangledStringRenderer } from "../renderers/TangledStringRenderer"; 4 + import type { TangledStringRecord } from "../types/tangled"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 7 /** 8 * Props for rendering Tangled String records. ··· 67 loadingIndicator, 68 colorScheme, 69 }) => { 70 + const { tangledBaseUrl } = useAtProto(); 71 const Comp: React.ComponentType<TangledStringRendererInjectedProps> = 72 renderer ?? ((props) => <TangledStringRenderer {...props} />); 73 const Wrapped: React.FC<{ ··· 80 colorScheme={colorScheme} 81 did={did} 82 rkey={rkey} 83 + canonicalUrl={`${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`} 84 /> 85 ); 86
+68 -6
lib/core/AtProtoRecord.tsx
··· 1 - import React from "react"; 2 import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 3 4 /** ··· 15 fallback?: React.ReactNode; 16 /** React node shown while the record is being fetched. */ 17 loadingIndicator?: React.ReactNode; 18 } 19 20 /** ··· 61 * 62 * When no custom renderer is provided, displays the record as formatted JSON. 63 * 64 * @example 65 * ```tsx 66 * // Fetch mode - retrieves record from network ··· 81 * /> 82 * ``` 83 * 84 * @param props - Either fetch props (did/collection/rkey) or prefetch props (record). 85 * @returns A rendered AT Protocol record with loading/error states handled. 86 */ ··· 89 renderer: Renderer, 90 fallback = null, 91 loadingIndicator = "Loadingโ€ฆ", 92 } = props; 93 const hasProvidedRecord = "record" in props; 94 const providedRecord = hasProvidedRecord ? props.record : undefined; 95 96 const { 97 record: fetchedRecord, 98 error, 99 loading, 100 } = useAtProtoRecord<T>({ 101 - did: hasProvidedRecord ? undefined : props.did, 102 - collection: hasProvidedRecord ? undefined : props.collection, 103 - rkey: hasProvidedRecord ? undefined : props.rkey, 104 }); 105 106 - const record = providedRecord ?? fetchedRecord; 107 - const isLoading = loading && !providedRecord; 108 109 if (error && !record) return <>{fallback}</>; 110 if (!record) return <>{isLoading ? loadingIndicator : fallback}</>;
··· 1 + import React, { useState, useEffect, useRef } from "react"; 2 import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 3 4 /** ··· 15 fallback?: React.ReactNode; 16 /** React node shown while the record is being fetched. */ 17 loadingIndicator?: React.ReactNode; 18 + /** Auto-refresh interval in milliseconds. When set, the record will be refetched at this interval. */ 19 + refreshInterval?: number; 20 + /** Comparison function to determine if a record has changed. Used to prevent unnecessary re-renders during auto-refresh. */ 21 + compareRecords?: (prev: T | undefined, next: T | undefined) => boolean; 22 } 23 24 /** ··· 65 * 66 * When no custom renderer is provided, displays the record as formatted JSON. 67 * 68 + * **Auto-refresh**: Set `refreshInterval` to automatically refetch the record at the specified interval. 69 + * The component intelligently avoids re-rendering if the record hasn't changed (using `compareRecords`). 70 + * 71 * @example 72 * ```tsx 73 * // Fetch mode - retrieves record from network ··· 88 * /> 89 * ``` 90 * 91 + * @example 92 + * ```tsx 93 + * // Auto-refresh mode - refetches every 15 seconds 94 + * <AtProtoRecord 95 + * did="did:plc:example" 96 + * collection="fm.teal.alpha.actor.status" 97 + * rkey="self" 98 + * refreshInterval={15000} 99 + * compareRecords={(prev, next) => JSON.stringify(prev) === JSON.stringify(next)} 100 + * renderer={MyCustomRenderer} 101 + * /> 102 + * ``` 103 + * 104 * @param props - Either fetch props (did/collection/rkey) or prefetch props (record). 105 * @returns A rendered AT Protocol record with loading/error states handled. 106 */ ··· 109 renderer: Renderer, 110 fallback = null, 111 loadingIndicator = "Loadingโ€ฆ", 112 + refreshInterval, 113 + compareRecords, 114 } = props; 115 const hasProvidedRecord = "record" in props; 116 const providedRecord = hasProvidedRecord ? props.record : undefined; 117 118 + // Extract fetch props for logging 119 + const fetchDid = hasProvidedRecord ? undefined : (props as any).did; 120 + const fetchCollection = hasProvidedRecord ? undefined : (props as any).collection; 121 + const fetchRkey = hasProvidedRecord ? undefined : (props as any).rkey; 122 + 123 + // State for managing auto-refresh 124 + const [refreshKey, setRefreshKey] = useState(0); 125 + const [stableRecord, setStableRecord] = useState<T | undefined>(providedRecord); 126 + const previousRecordRef = useRef<T | undefined>(providedRecord); 127 + 128 + // Auto-refresh interval 129 + useEffect(() => { 130 + if (!refreshInterval || hasProvidedRecord) return; 131 + 132 + const interval = setInterval(() => { 133 + setRefreshKey((prev) => prev + 1); 134 + }, refreshInterval); 135 + 136 + return () => clearInterval(interval); 137 + }, [refreshInterval, hasProvidedRecord, fetchCollection, fetchDid]); 138 + 139 const { 140 record: fetchedRecord, 141 error, 142 loading, 143 } = useAtProtoRecord<T>({ 144 + did: fetchDid, 145 + collection: fetchCollection, 146 + rkey: fetchRkey, 147 + bypassCache: !!refreshInterval && refreshKey > 0, // Bypass cache on auto-refresh (but not initial load) 148 + _refreshKey: refreshKey, // Force hook to re-run 149 }); 150 151 + // Determine which record to use 152 + const currentRecord = providedRecord ?? fetchedRecord; 153 + 154 + // Handle record changes with optional comparison 155 + useEffect(() => { 156 + if (!currentRecord) return; 157 + 158 + const hasChanged = compareRecords 159 + ? !compareRecords(previousRecordRef.current, currentRecord) 160 + : previousRecordRef.current !== currentRecord; 161 + 162 + if (hasChanged) { 163 + setStableRecord(currentRecord); 164 + previousRecordRef.current = currentRecord; 165 + } 166 + }, [currentRecord, compareRecords]); 167 + 168 + const record = stableRecord; 169 + const isLoading = loading && !providedRecord && !stableRecord; 170 171 if (error && !record) return <>{fallback}</>; 172 if (!record) return <>{isLoading ? loadingIndicator : fallback}</>;
+158 -29
lib/hooks/useAtProtoRecord.ts
··· 1 - import { useEffect, useState } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 import { createAtprotoClient } from "../utils/atproto-client"; 5 import { useBlueskyAppview } from "./useBlueskyAppview"; 6 7 /** 8 * Identifier trio required to address an AT Protocol record. ··· 14 collection?: string; 15 /** Record key string uniquely identifying the record within the collection. */ 16 rkey?: string; 17 } 18 19 /** ··· 41 * @param did - DID (or handle before resolution) that owns the record. 42 * @param collection - NSID collection from which to fetch the record. 43 * @param rkey - Record key identifying the record within the collection. 44 * @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag. 45 */ 46 export function useAtProtoRecord<T = unknown>({ 47 did: handleOrDid, 48 collection, 49 rkey, 50 }: AtProtoRecordKey): AtProtoRecordState<T> { 51 const isBlueskyCollection = collection?.startsWith("app.bsky."); 52 - 53 // Always call all hooks (React rules) - conditionally use results 54 const blueskyResult = useBlueskyAppview<T>({ 55 did: isBlueskyCollection ? handleOrDid : undefined, 56 collection: isBlueskyCollection ? collection : undefined, 57 rkey: isBlueskyCollection ? rkey : undefined, 58 }); 59 - 60 const { 61 did, 62 error: didError, ··· 70 const [state, setState] = useState<AtProtoRecordState<T>>({ 71 loading: !!(handleOrDid && collection && rkey), 72 }); 73 74 useEffect(() => { 75 let cancelled = false; ··· 87 }); 88 return () => { 89 cancelled = true; 90 }; 91 } 92 ··· 94 assignState({ loading: false, error: didError }); 95 return () => { 96 cancelled = true; 97 }; 98 } 99 ··· 101 assignState({ loading: false, error: endpointError }); 102 return () => { 103 cancelled = true; 104 }; 105 } 106 ··· 108 assignState({ loading: true, error: undefined }); 109 return () => { 110 cancelled = true; 111 }; 112 } 113 114 assignState({ loading: true, error: undefined, record: undefined }); 115 116 - (async () => { 117 - try { 118 - const { rpc } = await createAtprotoClient({ 119 - service: endpoint, 120 }); 121 - const res = await ( 122 - rpc as unknown as { 123 - get: ( 124 - nsid: string, 125 - opts: { 126 - params: { 127 - repo: string; 128 - collection: string; 129 - rkey: string; 130 - }; 131 - }, 132 - ) => Promise<{ ok: boolean; data: { value: T } }>; 133 } 134 - ).get("com.atproto.repo.getRecord", { 135 - params: { repo: did, collection, rkey }, 136 - }); 137 - if (!res.ok) throw new Error("Failed to load record"); 138 - const record = (res.data as { value: T }).value; 139 - assignState({ record, loading: false }); 140 - } catch (e) { 141 - const err = e instanceof Error ? e : new Error(String(e)); 142 - assignState({ error: err, loading: false }); 143 } 144 - })(); 145 146 return () => { 147 cancelled = true; 148 }; 149 }, [ 150 handleOrDid, ··· 156 resolvingEndpoint, 157 didError, 158 endpointError, 159 ]); 160 161 // Return Bluesky result for app.bsky.* collections
··· 1 + import { useEffect, useState, useRef } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 import { createAtprotoClient } from "../utils/atproto-client"; 5 import { useBlueskyAppview } from "./useBlueskyAppview"; 6 + import { useAtProto } from "../providers/AtProtoProvider"; 7 8 /** 9 * Identifier trio required to address an AT Protocol record. ··· 15 collection?: string; 16 /** Record key string uniquely identifying the record within the collection. */ 17 rkey?: string; 18 + /** Force bypass cache and refetch from network. Useful for auto-refresh scenarios. */ 19 + bypassCache?: boolean; 20 + /** Internal refresh trigger - changes to this value force a refetch. */ 21 + _refreshKey?: number; 22 } 23 24 /** ··· 46 * @param did - DID (or handle before resolution) that owns the record. 47 * @param collection - NSID collection from which to fetch the record. 48 * @param rkey - Record key identifying the record within the collection. 49 + * @param bypassCache - Force bypass cache and refetch from network. Useful for auto-refresh scenarios. 50 + * @param _refreshKey - Internal parameter used to trigger refetches. 51 * @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag. 52 */ 53 export function useAtProtoRecord<T = unknown>({ 54 did: handleOrDid, 55 collection, 56 rkey, 57 + bypassCache = false, 58 + _refreshKey = 0, 59 }: AtProtoRecordKey): AtProtoRecordState<T> { 60 + const { recordCache } = useAtProto(); 61 const isBlueskyCollection = collection?.startsWith("app.bsky."); 62 + 63 // Always call all hooks (React rules) - conditionally use results 64 const blueskyResult = useBlueskyAppview<T>({ 65 did: isBlueskyCollection ? handleOrDid : undefined, 66 collection: isBlueskyCollection ? collection : undefined, 67 rkey: isBlueskyCollection ? rkey : undefined, 68 }); 69 + 70 const { 71 did, 72 error: didError, ··· 80 const [state, setState] = useState<AtProtoRecordState<T>>({ 81 loading: !!(handleOrDid && collection && rkey), 82 }); 83 + 84 + const releaseRef = useRef<(() => void) | undefined>(undefined); 85 86 useEffect(() => { 87 let cancelled = false; ··· 99 }); 100 return () => { 101 cancelled = true; 102 + if (releaseRef.current) { 103 + releaseRef.current(); 104 + releaseRef.current = undefined; 105 + } 106 }; 107 } 108 ··· 110 assignState({ loading: false, error: didError }); 111 return () => { 112 cancelled = true; 113 + if (releaseRef.current) { 114 + releaseRef.current(); 115 + releaseRef.current = undefined; 116 + } 117 }; 118 } 119 ··· 121 assignState({ loading: false, error: endpointError }); 122 return () => { 123 cancelled = true; 124 + if (releaseRef.current) { 125 + releaseRef.current(); 126 + releaseRef.current = undefined; 127 + } 128 }; 129 } 130 ··· 132 assignState({ loading: true, error: undefined }); 133 return () => { 134 cancelled = true; 135 + if (releaseRef.current) { 136 + releaseRef.current(); 137 + releaseRef.current = undefined; 138 + } 139 }; 140 } 141 142 assignState({ loading: true, error: undefined, record: undefined }); 143 144 + // Bypass cache if requested (for auto-refresh scenarios) 145 + if (bypassCache) { 146 + assignState({ loading: true, error: undefined }); 147 + 148 + // Skip cache and fetch directly 149 + const controller = new AbortController(); 150 + 151 + const fetchPromise = (async () => { 152 + try { 153 + const { rpc } = await createAtprotoClient({ 154 + service: endpoint, 155 + }); 156 + const res = await ( 157 + rpc as unknown as { 158 + get: ( 159 + nsid: string, 160 + opts: { 161 + params: { 162 + repo: string; 163 + collection: string; 164 + rkey: string; 165 + }; 166 + }, 167 + ) => Promise<{ ok: boolean; data: { value: T } }>; 168 + } 169 + ).get("com.atproto.repo.getRecord", { 170 + params: { repo: did, collection, rkey }, 171 + }); 172 + if (!res.ok) throw new Error("Failed to load record"); 173 + return (res.data as { value: T }).value; 174 + } catch (err) { 175 + // Provide helpful error for banned/unreachable Bluesky PDSes 176 + if (endpoint.includes('.bsky.network')) { 177 + throw new Error( 178 + `Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.` 179 + ); 180 + } 181 + throw err; 182 + } 183 + })(); 184 + 185 + fetchPromise 186 + .then((record) => { 187 + if (!cancelled) { 188 + assignState({ record, loading: false }); 189 + } 190 + }) 191 + .catch((e) => { 192 + if (!cancelled) { 193 + const err = e instanceof Error ? e : new Error(String(e)); 194 + assignState({ error: err, loading: false }); 195 + } 196 }); 197 + 198 + return () => { 199 + cancelled = true; 200 + controller.abort(); 201 + }; 202 + } 203 + 204 + // Use recordCache.ensure for deduplication and caching 205 + const { promise, release } = recordCache.ensure<T>( 206 + did, 207 + collection, 208 + rkey, 209 + () => { 210 + const controller = new AbortController(); 211 + 212 + const fetchPromise = (async () => { 213 + try { 214 + const { rpc } = await createAtprotoClient({ 215 + service: endpoint, 216 + }); 217 + const res = await ( 218 + rpc as unknown as { 219 + get: ( 220 + nsid: string, 221 + opts: { 222 + params: { 223 + repo: string; 224 + collection: string; 225 + rkey: string; 226 + }; 227 + }, 228 + ) => Promise<{ ok: boolean; data: { value: T } }>; 229 + } 230 + ).get("com.atproto.repo.getRecord", { 231 + params: { repo: did, collection, rkey }, 232 + }); 233 + if (!res.ok) throw new Error("Failed to load record"); 234 + return (res.data as { value: T }).value; 235 + } catch (err) { 236 + // Provide helpful error for banned/unreachable Bluesky PDSes 237 + if (endpoint.includes('.bsky.network')) { 238 + throw new Error( 239 + `Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.` 240 + ); 241 + } 242 + throw err; 243 } 244 + })(); 245 + 246 + return { 247 + promise: fetchPromise, 248 + abort: () => controller.abort(), 249 + }; 250 } 251 + ); 252 + 253 + releaseRef.current = release; 254 + 255 + promise 256 + .then((record) => { 257 + if (!cancelled) { 258 + assignState({ record, loading: false }); 259 + } 260 + }) 261 + .catch((e) => { 262 + if (!cancelled) { 263 + const err = e instanceof Error ? e : new Error(String(e)); 264 + assignState({ error: err, loading: false }); 265 + } 266 + }); 267 268 return () => { 269 cancelled = true; 270 + if (releaseRef.current) { 271 + releaseRef.current(); 272 + releaseRef.current = undefined; 273 + } 274 }; 275 }, [ 276 handleOrDid, ··· 282 resolvingEndpoint, 283 didError, 284 endpointError, 285 + recordCache, 286 + bypassCache, 287 + _refreshKey, 288 ]); 289 290 // Return Bluesky result for app.bsky.* collections
+163
lib/hooks/useBacklinks.ts
···
··· 1 + import { useEffect, useState, useCallback, useRef } from "react"; 2 + 3 + /** 4 + * Individual backlink record returned by Microcosm Constellation. 5 + */ 6 + export interface BacklinkRecord { 7 + /** DID of the author who created the backlink. */ 8 + did: string; 9 + /** Collection type of the backlink record (e.g., "sh.tangled.feed.star"). */ 10 + collection: string; 11 + /** Record key of the backlink. */ 12 + rkey: string; 13 + } 14 + 15 + /** 16 + * Response from Microcosm Constellation API. 17 + */ 18 + export interface BacklinksResponse { 19 + /** Total count of backlinks. */ 20 + total: number; 21 + /** Array of backlink records. */ 22 + records: BacklinkRecord[]; 23 + /** Cursor for pagination (optional). */ 24 + cursor?: string; 25 + } 26 + 27 + /** 28 + * Parameters for fetching backlinks. 29 + */ 30 + export interface UseBacklinksParams { 31 + /** The AT-URI subject to get backlinks for (e.g., "at://did:plc:xxx/sh.tangled.repo/yyy"). */ 32 + subject: string; 33 + /** The source collection and path (e.g., "sh.tangled.feed.star:subject"). */ 34 + source: string; 35 + /** Maximum number of results to fetch (default: 16, max: 100). */ 36 + limit?: number; 37 + /** Base URL for the Microcosm Constellation API. */ 38 + constellationBaseUrl?: string; 39 + /** Whether to automatically fetch backlinks on mount. */ 40 + enabled?: boolean; 41 + } 42 + 43 + const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue"; 44 + 45 + /** 46 + * Hook to fetch backlinks from Microcosm Constellation API. 47 + * 48 + * Backlinks are records that reference another record. For example, 49 + * `sh.tangled.feed.star` records are backlinks to `sh.tangled.repo` records, 50 + * representing users who have starred a repository. 51 + * 52 + * @param params - Configuration for fetching backlinks 53 + * @returns Object containing backlinks data, loading state, error, and refetch function 54 + * 55 + * @example 56 + * ```tsx 57 + * const { backlinks, loading, error, count } = useBacklinks({ 58 + * subject: "at://did:plc:example/sh.tangled.repo/3k2aexample", 59 + * source: "sh.tangled.feed.star:subject", 60 + * }); 61 + * ``` 62 + */ 63 + export function useBacklinks({ 64 + subject, 65 + source, 66 + limit = 16, 67 + constellationBaseUrl = DEFAULT_CONSTELLATION, 68 + enabled = true, 69 + }: UseBacklinksParams) { 70 + const [backlinks, setBacklinks] = useState<BacklinkRecord[]>([]); 71 + const [total, setTotal] = useState(0); 72 + const [loading, setLoading] = useState(false); 73 + const [error, setError] = useState<Error | undefined>(undefined); 74 + const [cursor, setCursor] = useState<string | undefined>(undefined); 75 + const abortControllerRef = useRef<AbortController | null>(null); 76 + 77 + const fetchBacklinks = useCallback( 78 + async (signal?: AbortSignal) => { 79 + if (!subject || !source || !enabled) return; 80 + 81 + try { 82 + setLoading(true); 83 + setError(undefined); 84 + 85 + const baseUrl = constellationBaseUrl.endsWith("/") 86 + ? constellationBaseUrl.slice(0, -1) 87 + : constellationBaseUrl; 88 + 89 + const params = new URLSearchParams({ 90 + subject: subject, 91 + source: source, 92 + limit: limit.toString(), 93 + }); 94 + 95 + const url = `${baseUrl}/xrpc/blue.microcosm.links.getBacklinks?${params}`; 96 + 97 + const response = await fetch(url, { signal }); 98 + 99 + if (!response.ok) { 100 + throw new Error( 101 + `Failed to fetch backlinks: ${response.status} ${response.statusText}`, 102 + ); 103 + } 104 + 105 + const data: BacklinksResponse = await response.json(); 106 + setBacklinks(data.records || []); 107 + setTotal(data.total || 0); 108 + setCursor(data.cursor); 109 + } catch (err) { 110 + if (err instanceof Error && err.name === "AbortError") { 111 + // Ignore abort errors 112 + return; 113 + } 114 + setError( 115 + err instanceof Error ? err : new Error("Unknown error fetching backlinks"), 116 + ); 117 + } finally { 118 + setLoading(false); 119 + } 120 + }, 121 + [subject, source, limit, constellationBaseUrl, enabled], 122 + ); 123 + 124 + const refetch = useCallback(() => { 125 + // Abort any in-flight request 126 + if (abortControllerRef.current) { 127 + abortControllerRef.current.abort(); 128 + } 129 + 130 + const controller = new AbortController(); 131 + abortControllerRef.current = controller; 132 + fetchBacklinks(controller.signal); 133 + }, [fetchBacklinks]); 134 + 135 + useEffect(() => { 136 + if (!enabled) return; 137 + 138 + const controller = new AbortController(); 139 + abortControllerRef.current = controller; 140 + fetchBacklinks(controller.signal); 141 + 142 + return () => { 143 + controller.abort(); 144 + }; 145 + }, [fetchBacklinks, enabled]); 146 + 147 + return { 148 + /** Array of backlink records. */ 149 + backlinks, 150 + /** Whether backlinks are currently being fetched. */ 151 + loading, 152 + /** Error if fetch failed. */ 153 + error, 154 + /** Pagination cursor (not yet implemented for pagination). */ 155 + cursor, 156 + /** Total count of backlinks from the API. */ 157 + total, 158 + /** Total count of backlinks (alias for total). */ 159 + count: total, 160 + /** Function to manually refetch backlinks. */ 161 + refetch, 162 + }; 163 + }
+140 -74
lib/hooks/useBlueskyAppview.ts
··· 1 - import { useEffect, useReducer } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 - import { createAtprotoClient, SLINGSHOT_BASE_URL } from "../utils/atproto-client"; 5 6 /** 7 * Extended blob reference that includes CDN URL from appview responses. ··· 90 /** Source from which the record was successfully fetched. */ 91 source?: "appview" | "slingshot" | "pds"; 92 } 93 - 94 - export const DEFAULT_APPVIEW_SERVICE = "https://public.api.bsky.app"; 95 96 /** 97 * Maps Bluesky collection NSIDs to their corresponding appview API endpoints. ··· 235 appviewService, 236 skipAppview = false, 237 }: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> { 238 const { 239 did, 240 error: didError, ··· 253 source: undefined, 254 }); 255 256 useEffect(() => { 257 let cancelled = false; 258 ··· 261 if (!cancelled) dispatch({ type: "RESET" }); 262 return () => { 263 cancelled = true; 264 }; 265 } 266 ··· 268 if (!cancelled) dispatch({ type: "SET_ERROR", error: didError }); 269 return () => { 270 cancelled = true; 271 }; 272 } 273 ··· 275 if (!cancelled) dispatch({ type: "SET_ERROR", error: endpointError }); 276 return () => { 277 cancelled = true; 278 }; 279 } 280 ··· 282 if (!cancelled) dispatch({ type: "SET_LOADING", loading: true }); 283 return () => { 284 cancelled = true; 285 }; 286 } 287 288 // Start fetching 289 dispatch({ type: "SET_LOADING", loading: true }); 290 291 - (async () => { 292 - let lastError: Error | undefined; 293 294 - // Tier 1: Try Bluesky appview API 295 - if (!skipAppview && BLUESKY_COLLECTION_TO_ENDPOINT[collection]) { 296 - try { 297 - const result = await fetchFromAppview<T>( 298 - did, 299 - collection, 300 - rkey, 301 - appviewService ?? DEFAULT_APPVIEW_SERVICE, 302 - ); 303 - if (!cancelled && result) { 304 - dispatch({ 305 - type: "SET_SUCCESS", 306 - record: result, 307 - source: "appview", 308 - }); 309 - return; 310 } 311 - } catch (err) { 312 - lastError = err as Error; 313 - // Continue to next tier 314 - } 315 } 316 317 - // Tier 2: Try Slingshot getRecord 318 - try { 319 - const result = await fetchFromSlingshot<T>(did, collection, rkey); 320 - if (!cancelled && result) { 321 dispatch({ 322 type: "SET_SUCCESS", 323 - record: result, 324 - source: "slingshot", 325 }); 326 - return; 327 } 328 - } catch (err) { 329 - lastError = err as Error; 330 - // Continue to next tier 331 - } 332 - 333 - // Tier 3: Try PDS directly 334 - try { 335 - const result = await fetchFromPds<T>( 336 - did, 337 - collection, 338 - rkey, 339 - pdsEndpoint, 340 - ); 341 - if (!cancelled && result) { 342 dispatch({ 343 - type: "SET_SUCCESS", 344 - record: result, 345 - source: "pds", 346 }); 347 - return; 348 } 349 - } catch (err) { 350 - lastError = err as Error; 351 - } 352 - 353 - // All tiers failed 354 - if (!cancelled) { 355 - dispatch({ 356 - type: "SET_ERROR", 357 - error: 358 - lastError ?? 359 - new Error("Failed to fetch record from all sources"), 360 - }); 361 - } 362 - })(); 363 364 return () => { 365 cancelled = true; 366 }; 367 }, [ 368 handleOrDid, ··· 370 collection, 371 rkey, 372 pdsEndpoint, 373 - appviewService, 374 skipAppview, 375 resolvingDid, 376 resolvingEndpoint, 377 didError, 378 endpointError, 379 ]); 380 381 return state; ··· 536 did: string, 537 collection: string, 538 rkey: string, 539 ): Promise<T | undefined> { 540 - const res = await callGetRecord<T>(SLINGSHOT_BASE_URL, did, collection, rkey); 541 if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`); 542 return res.data.value; 543 } ··· 636 }; 637 }> { 638 const { rpc } = await createAtprotoClient({ service }); 639 return await (rpc as unknown as { 640 get: ( 641 nsid: string, ··· 648 }; 649 }>; 650 }).get("com.atproto.repo.listRecords", { 651 - params: { 652 - repo: did, 653 - collection, 654 - limit, 655 - cursor, 656 - reverse: false, 657 - }, 658 }); 659 } 660
··· 1 + import { useEffect, useReducer, useRef } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { createAtprotoClient } from "../utils/atproto-client"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 7 /** 8 * Extended blob reference that includes CDN URL from appview responses. ··· 91 /** Source from which the record was successfully fetched. */ 92 source?: "appview" | "slingshot" | "pds"; 93 } 94 95 /** 96 * Maps Bluesky collection NSIDs to their corresponding appview API endpoints. ··· 234 appviewService, 235 skipAppview = false, 236 }: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> { 237 + const { recordCache, blueskyAppviewService, resolver } = useAtProto(); 238 + const effectiveAppviewService = appviewService ?? blueskyAppviewService; 239 + 240 + // Only use this hook for Bluesky collections (app.bsky.*) 241 + const isBlueskyCollection = collection?.startsWith("app.bsky."); 242 + 243 const { 244 did, 245 error: didError, ··· 258 source: undefined, 259 }); 260 261 + const releaseRef = useRef<(() => void) | undefined>(undefined); 262 + 263 useEffect(() => { 264 let cancelled = false; 265 ··· 268 if (!cancelled) dispatch({ type: "RESET" }); 269 return () => { 270 cancelled = true; 271 + if (releaseRef.current) { 272 + releaseRef.current(); 273 + releaseRef.current = undefined; 274 + } 275 + }; 276 + } 277 + 278 + // Return early if not a Bluesky collection - this hook should not be used for other lexicons 279 + if (!isBlueskyCollection) { 280 + if (!cancelled) dispatch({ type: "RESET" }); 281 + return () => { 282 + cancelled = true; 283 + if (releaseRef.current) { 284 + releaseRef.current(); 285 + releaseRef.current = undefined; 286 + } 287 }; 288 } 289 ··· 291 if (!cancelled) dispatch({ type: "SET_ERROR", error: didError }); 292 return () => { 293 cancelled = true; 294 + if (releaseRef.current) { 295 + releaseRef.current(); 296 + releaseRef.current = undefined; 297 + } 298 }; 299 } 300 ··· 302 if (!cancelled) dispatch({ type: "SET_ERROR", error: endpointError }); 303 return () => { 304 cancelled = true; 305 + if (releaseRef.current) { 306 + releaseRef.current(); 307 + releaseRef.current = undefined; 308 + } 309 }; 310 } 311 ··· 313 if (!cancelled) dispatch({ type: "SET_LOADING", loading: true }); 314 return () => { 315 cancelled = true; 316 + if (releaseRef.current) { 317 + releaseRef.current(); 318 + releaseRef.current = undefined; 319 + } 320 }; 321 } 322 323 // Start fetching 324 dispatch({ type: "SET_LOADING", loading: true }); 325 326 + // Use recordCache.ensure for deduplication and caching 327 + const { promise, release } = recordCache.ensure<{ record: T; source: "appview" | "slingshot" | "pds" }>( 328 + did, 329 + collection, 330 + rkey, 331 + () => { 332 + const controller = new AbortController(); 333 + 334 + const fetchPromise = (async (): Promise<{ record: T; source: "appview" | "slingshot" | "pds" }> => { 335 + let lastError: Error | undefined; 336 + 337 + // Tier 1: Try Bluesky appview API 338 + if (!skipAppview && BLUESKY_COLLECTION_TO_ENDPOINT[collection]) { 339 + try { 340 + const result = await fetchFromAppview<T>( 341 + did, 342 + collection, 343 + rkey, 344 + effectiveAppviewService, 345 + ); 346 + if (result) { 347 + return { record: result, source: "appview" }; 348 + } 349 + } catch (err) { 350 + lastError = err as Error; 351 + // Continue to next tier 352 + } 353 + } 354 355 + // Tier 2: Try Slingshot getRecord 356 + try { 357 + const slingshotUrl = resolver.getSlingshotUrl(); 358 + const result = await fetchFromSlingshot<T>(did, collection, rkey, slingshotUrl); 359 + if (result) { 360 + return { record: result, source: "slingshot" }; 361 + } 362 + } catch (err) { 363 + lastError = err as Error; 364 + // Continue to next tier 365 } 366 + 367 + // Tier 3: Try PDS directly 368 + try { 369 + const result = await fetchFromPds<T>( 370 + did, 371 + collection, 372 + rkey, 373 + pdsEndpoint, 374 + ); 375 + if (result) { 376 + return { record: result, source: "pds" }; 377 + } 378 + } catch (err) { 379 + lastError = err as Error; 380 + } 381 + 382 + // All tiers failed - provide helpful error for banned/unreachable Bluesky PDSes 383 + if (pdsEndpoint.includes('.bsky.network')) { 384 + throw new Error( 385 + `Record unavailable. The Bluesky PDS (${pdsEndpoint}) may be unreachable or the account may be banned.` 386 + ); 387 + } 388 + 389 + throw lastError ?? new Error("Failed to fetch record from all sources"); 390 + })(); 391 + 392 + return { 393 + promise: fetchPromise, 394 + abort: () => controller.abort(), 395 + }; 396 } 397 + ); 398 399 + releaseRef.current = release; 400 + 401 + promise 402 + .then(({ record, source }) => { 403 + if (!cancelled) { 404 dispatch({ 405 type: "SET_SUCCESS", 406 + record, 407 + source, 408 }); 409 } 410 + }) 411 + .catch((err) => { 412 + if (!cancelled) { 413 dispatch({ 414 + type: "SET_ERROR", 415 + error: err instanceof Error ? err : new Error(String(err)), 416 }); 417 } 418 + }); 419 420 return () => { 421 cancelled = true; 422 + if (releaseRef.current) { 423 + releaseRef.current(); 424 + releaseRef.current = undefined; 425 + } 426 }; 427 }, [ 428 handleOrDid, ··· 430 collection, 431 rkey, 432 pdsEndpoint, 433 + effectiveAppviewService, 434 skipAppview, 435 resolvingDid, 436 resolvingEndpoint, 437 didError, 438 endpointError, 439 + recordCache, 440 + resolver, 441 ]); 442 443 return state; ··· 598 did: string, 599 collection: string, 600 rkey: string, 601 + slingshotBaseUrl: string, 602 ): Promise<T | undefined> { 603 + const res = await callGetRecord<T>(slingshotBaseUrl, did, collection, rkey); 604 if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`); 605 return res.data.value; 606 } ··· 699 }; 700 }> { 701 const { rpc } = await createAtprotoClient({ service }); 702 + 703 + const params: Record<string, unknown> = { 704 + repo: did, 705 + collection, 706 + limit, 707 + cursor, 708 + reverse: false, 709 + }; 710 + 711 return await (rpc as unknown as { 712 get: ( 713 nsid: string, ··· 720 }; 721 }>; 722 }).get("com.atproto.repo.listRecords", { 723 + params, 724 }); 725 } 726
+5 -2
lib/hooks/useLatestRecord.ts
··· 21 22 /** 23 * Fetches the most recent record from a collection using `listRecords(limit=3)`. 24 - * 25 * Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly. 26 - * 27 * Records with invalid timestamps (before 2023, when ATProto was created) are automatically 28 * skipped, and additional records are fetched to find a valid one. 29 * 30 * @param handleOrDid - Handle or DID that owns the collection. 31 * @param collection - NSID of the collection to query. 32 * @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error. 33 */ 34 export function useLatestRecord<T = unknown>( 35 handleOrDid: string | undefined, 36 collection: string, 37 ): LatestRecordState<T> { 38 const { 39 did, ··· 157 resolvingEndpoint, 158 didError, 159 endpointError, 160 ]); 161 162 return state;
··· 21 22 /** 23 * Fetches the most recent record from a collection using `listRecords(limit=3)`. 24 + * 25 * Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly. 26 + * 27 * Records with invalid timestamps (before 2023, when ATProto was created) are automatically 28 * skipped, and additional records are fetched to find a valid one. 29 * 30 * @param handleOrDid - Handle or DID that owns the collection. 31 * @param collection - NSID of the collection to query. 32 + * @param refreshKey - Optional key that when changed, triggers a refetch. Use for auto-refresh scenarios. 33 * @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error. 34 */ 35 export function useLatestRecord<T = unknown>( 36 handleOrDid: string | undefined, 37 collection: string, 38 + refreshKey?: number, 39 ): LatestRecordState<T> { 40 const { 41 did, ··· 159 resolvingEndpoint, 160 didError, 161 endpointError, 162 + refreshKey, 163 ]); 164 165 return state;
+4 -6
lib/hooks/usePaginatedRecords.ts
··· 1 import { useCallback, useEffect, useMemo, useRef, useState } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 - import { 5 - DEFAULT_APPVIEW_SERVICE, 6 - callAppviewRpc, 7 - callListRecords 8 - } from "./useBlueskyAppview"; 9 10 /** 11 * Record envelope returned by paginated AT Protocol queries. ··· 118 authorFeedService, 119 authorFeedActor, 120 }: UsePaginatedRecordsOptions): UsePaginatedRecordsResult<T> { 121 const { 122 did, 123 handle, ··· 213 } 214 215 const res = await callAppviewRpc<AuthorFeedResponse>( 216 - authorFeedService ?? DEFAULT_APPVIEW_SERVICE, 217 "app.bsky.feed.getAuthorFeed", 218 { 219 actor: actorIdentifier,
··· 1 import { useCallback, useEffect, useMemo, useRef, useState } from "react"; 2 import { useDidResolution } from "./useDidResolution"; 3 import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { callAppviewRpc, callListRecords } from "./useBlueskyAppview"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 7 /** 8 * Record envelope returned by paginated AT Protocol queries. ··· 115 authorFeedService, 116 authorFeedActor, 117 }: UsePaginatedRecordsOptions): UsePaginatedRecordsResult<T> { 118 + const { blueskyAppviewService } = useAtProto(); 119 const { 120 did, 121 handle, ··· 211 } 212 213 const res = await callAppviewRpc<AuthorFeedResponse>( 214 + authorFeedService ?? blueskyAppviewService, 215 "app.bsky.feed.getAuthorFeed", 216 { 217 actor: actorIdentifier,
+104
lib/hooks/useRepoLanguages.ts
···
··· 1 + import { useState, useEffect } from "react"; 2 + import type { RepoLanguagesResponse } from "../types/tangled"; 3 + 4 + export interface UseRepoLanguagesOptions { 5 + /** The knot server URL (e.g., "knot.gaze.systems") */ 6 + knot?: string; 7 + /** DID of the repository owner */ 8 + did?: string; 9 + /** Repository name */ 10 + repoName?: string; 11 + /** Branch to query (defaults to trying "main", then "master") */ 12 + branch?: string; 13 + /** Whether to enable the query */ 14 + enabled?: boolean; 15 + } 16 + 17 + export interface UseRepoLanguagesResult { 18 + /** Language data from the knot server */ 19 + data?: RepoLanguagesResponse; 20 + /** Loading state */ 21 + loading: boolean; 22 + /** Error state */ 23 + error?: Error; 24 + } 25 + 26 + /** 27 + * Hook to fetch repository language information from a Tangled knot server. 28 + * If no branch supplied, tries "main" first, then falls back to "master". 29 + */ 30 + export function useRepoLanguages({ 31 + knot, 32 + did, 33 + repoName, 34 + branch, 35 + enabled = true, 36 + }: UseRepoLanguagesOptions): UseRepoLanguagesResult { 37 + const [data, setData] = useState<RepoLanguagesResponse | undefined>(); 38 + const [loading, setLoading] = useState(false); 39 + const [error, setError] = useState<Error | undefined>(); 40 + 41 + useEffect(() => { 42 + if (!enabled || !knot || !did || !repoName) { 43 + return; 44 + } 45 + 46 + let cancelled = false; 47 + 48 + const fetchLanguages = async (ref: string): Promise<boolean> => { 49 + try { 50 + const url = `https://${knot}/xrpc/sh.tangled.repo.languages?repo=${encodeURIComponent(`${did}/${repoName}`)}&ref=${encodeURIComponent(ref)}`; 51 + const response = await fetch(url); 52 + 53 + if (!response.ok) { 54 + return false; 55 + } 56 + 57 + const result = await response.json(); 58 + if (!cancelled) { 59 + setData(result); 60 + setError(undefined); 61 + } 62 + return true; 63 + } catch (err) { 64 + return false; 65 + } 66 + }; 67 + 68 + const fetchWithFallback = async () => { 69 + setLoading(true); 70 + setError(undefined); 71 + 72 + if (branch) { 73 + const success = await fetchLanguages(branch); 74 + if (!cancelled) { 75 + if (!success) { 76 + setError(new Error(`Failed to fetch languages for branch: ${branch}`)); 77 + } 78 + setLoading(false); 79 + } 80 + } else { 81 + // Try "main" first, then "master" 82 + let success = await fetchLanguages("main"); 83 + if (!success && !cancelled) { 84 + success = await fetchLanguages("master"); 85 + } 86 + 87 + if (!cancelled) { 88 + if (!success) { 89 + setError(new Error("Failed to fetch languages for main or master branch")); 90 + } 91 + setLoading(false); 92 + } 93 + } 94 + }; 95 + 96 + fetchWithFallback(); 97 + 98 + return () => { 99 + cancelled = true; 100 + }; 101 + }, [knot, did, repoName, branch, enabled]); 102 + 103 + return { data, loading, error }; 104 + }
+13
lib/index.ts
··· 12 export * from "./components/BlueskyPostList"; 13 export * from "./components/BlueskyProfile"; 14 export * from "./components/BlueskyQuotePost"; 15 export * from "./components/LeafletDocument"; 16 export * from "./components/TangledString"; 17 18 // Hooks 19 export * from "./hooks/useAtProtoRecord"; 20 export * from "./hooks/useBlob"; 21 export * from "./hooks/useBlueskyAppview"; 22 export * from "./hooks/useBlueskyProfile"; ··· 24 export * from "./hooks/useLatestRecord"; 25 export * from "./hooks/usePaginatedRecords"; 26 export * from "./hooks/usePdsEndpoint"; 27 28 // Renderers 29 export * from "./renderers/BlueskyPostRenderer"; 30 export * from "./renderers/BlueskyProfileRenderer"; 31 export * from "./renderers/LeafletDocumentRenderer"; 32 export * from "./renderers/TangledStringRenderer"; 33 34 // Types 35 export * from "./types/bluesky"; 36 export * from "./types/leaflet"; 37 export * from "./types/theme"; 38 39 // Utilities
··· 12 export * from "./components/BlueskyPostList"; 13 export * from "./components/BlueskyProfile"; 14 export * from "./components/BlueskyQuotePost"; 15 + export * from "./components/GrainGallery"; 16 export * from "./components/LeafletDocument"; 17 + export * from "./components/TangledRepo"; 18 export * from "./components/TangledString"; 19 + export * from "./components/CurrentlyPlaying"; 20 + export * from "./components/LastPlayed"; 21 + export * from "./components/SongHistoryList"; 22 23 // Hooks 24 export * from "./hooks/useAtProtoRecord"; 25 + export * from "./hooks/useBacklinks"; 26 export * from "./hooks/useBlob"; 27 export * from "./hooks/useBlueskyAppview"; 28 export * from "./hooks/useBlueskyProfile"; ··· 30 export * from "./hooks/useLatestRecord"; 31 export * from "./hooks/usePaginatedRecords"; 32 export * from "./hooks/usePdsEndpoint"; 33 + export * from "./hooks/useRepoLanguages"; 34 35 // Renderers 36 export * from "./renderers/BlueskyPostRenderer"; 37 export * from "./renderers/BlueskyProfileRenderer"; 38 + export * from "./renderers/GrainGalleryRenderer"; 39 export * from "./renderers/LeafletDocumentRenderer"; 40 + export * from "./renderers/TangledRepoRenderer"; 41 export * from "./renderers/TangledStringRenderer"; 42 + export * from "./renderers/CurrentlyPlayingRenderer"; 43 44 // Types 45 export * from "./types/bluesky"; 46 + export * from "./types/grain"; 47 export * from "./types/leaflet"; 48 + export * from "./types/tangled"; 49 + export * from "./types/teal"; 50 export * from "./types/theme"; 51 52 // Utilities
+102 -9
lib/providers/AtProtoProvider.tsx
··· 5 useMemo, 6 useRef, 7 } from "react"; 8 - import { ServiceResolver, normalizeBaseUrl } from "../utils/atproto-client"; 9 - import { BlobCache, DidCache } from "../utils/cache"; 10 11 /** 12 * Props for the AT Protocol context provider. ··· 16 children: React.ReactNode; 17 /** Optional custom PLC directory URL. Defaults to https://plc.directory */ 18 plcDirectory?: string; 19 } 20 21 /** ··· 26 resolver: ServiceResolver; 27 /** Normalized PLC directory base URL. */ 28 plcDirectory: string; 29 /** Cache for DID documents and handle mappings. */ 30 didCache: DidCache; 31 /** Cache for fetched blob data. */ 32 blobCache: BlobCache; 33 } 34 35 const AtProtoContext = createContext<AtProtoContextValue | undefined>( ··· 75 export function AtProtoProvider({ 76 children, 77 plcDirectory, 78 }: AtProtoProviderProps) { 79 const normalizedPlc = useMemo( 80 () => 81 normalizeBaseUrl( 82 plcDirectory && plcDirectory.trim() 83 ? plcDirectory 84 - : "https://plc.directory", 85 ), 86 [plcDirectory], 87 ); 88 const resolver = useMemo( 89 - () => new ServiceResolver({ plcDirectory: normalizedPlc }), 90 - [normalizedPlc], 91 ); 92 const cachesRef = useRef<{ 93 didCache: DidCache; 94 blobCache: BlobCache; 95 } | null>(null); 96 if (!cachesRef.current) { 97 cachesRef.current = { 98 didCache: new DidCache(), 99 blobCache: new BlobCache(), 100 }; 101 } 102 ··· 104 () => ({ 105 resolver, 106 plcDirectory: normalizedPlc, 107 didCache: cachesRef.current!.didCache, 108 blobCache: cachesRef.current!.blobCache, 109 }), 110 - [resolver, normalizedPlc], 111 ); 112 113 return ( ··· 120 /** 121 * Hook that accesses the AT Protocol context provided by `AtProtoProvider`. 122 * 123 - * This hook exposes the service resolver, DID cache, and blob cache for building 124 - * custom AT Protocol functionality. 125 * 126 * @throws {Error} When called outside of an `AtProtoProvider`. 127 * @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL. ··· 131 * import { useAtProto } from 'atproto-ui'; 132 * 133 * function MyCustomComponent() { 134 - * const { resolver, didCache, blobCache } = useAtProto(); 135 * // Use the resolver and caches for custom AT Protocol operations 136 * } 137 * ```
··· 5 useMemo, 6 useRef, 7 } from "react"; 8 + import { ServiceResolver, normalizeBaseUrl, DEFAULT_CONFIG } from "../utils/atproto-client"; 9 + import { BlobCache, DidCache, RecordCache } from "../utils/cache"; 10 11 /** 12 * Props for the AT Protocol context provider. ··· 16 children: React.ReactNode; 17 /** Optional custom PLC directory URL. Defaults to https://plc.directory */ 18 plcDirectory?: string; 19 + /** Optional custom identity service URL. Defaults to https://public.api.bsky.app */ 20 + identityService?: string; 21 + /** Optional custom Slingshot service URL. Defaults to https://slingshot.microcosm.blue */ 22 + slingshotBaseUrl?: string; 23 + /** Optional custom Bluesky appview service URL. Defaults to https://public.api.bsky.app */ 24 + blueskyAppviewService?: string; 25 + /** Optional custom Bluesky app base URL for links. Defaults to https://bsky.app */ 26 + blueskyAppBaseUrl?: string; 27 + /** Optional custom Tangled base URL for links. Defaults to https://tangled.org */ 28 + tangledBaseUrl?: string; 29 + /** Optional custom Constellation API URL for backlinks. Defaults to https://constellation.microcosm.blue */ 30 + constellationBaseUrl?: string; 31 } 32 33 /** ··· 38 resolver: ServiceResolver; 39 /** Normalized PLC directory base URL. */ 40 plcDirectory: string; 41 + /** Normalized Bluesky appview service URL. */ 42 + blueskyAppviewService: string; 43 + /** Normalized Bluesky app base URL for links. */ 44 + blueskyAppBaseUrl: string; 45 + /** Normalized Tangled base URL for links. */ 46 + tangledBaseUrl: string; 47 + /** Normalized Constellation API base URL for backlinks. */ 48 + constellationBaseUrl: string; 49 /** Cache for DID documents and handle mappings. */ 50 didCache: DidCache; 51 /** Cache for fetched blob data. */ 52 blobCache: BlobCache; 53 + /** Cache for fetched AT Protocol records. */ 54 + recordCache: RecordCache; 55 } 56 57 const AtProtoContext = createContext<AtProtoContextValue | undefined>( ··· 97 export function AtProtoProvider({ 98 children, 99 plcDirectory, 100 + identityService, 101 + slingshotBaseUrl, 102 + blueskyAppviewService, 103 + blueskyAppBaseUrl, 104 + tangledBaseUrl, 105 + constellationBaseUrl, 106 }: AtProtoProviderProps) { 107 const normalizedPlc = useMemo( 108 () => 109 normalizeBaseUrl( 110 plcDirectory && plcDirectory.trim() 111 ? plcDirectory 112 + : DEFAULT_CONFIG.plcDirectory, 113 ), 114 [plcDirectory], 115 ); 116 + const normalizedIdentity = useMemo( 117 + () => 118 + normalizeBaseUrl( 119 + identityService && identityService.trim() 120 + ? identityService 121 + : DEFAULT_CONFIG.identityService, 122 + ), 123 + [identityService], 124 + ); 125 + const normalizedSlingshot = useMemo( 126 + () => 127 + normalizeBaseUrl( 128 + slingshotBaseUrl && slingshotBaseUrl.trim() 129 + ? slingshotBaseUrl 130 + : DEFAULT_CONFIG.slingshotBaseUrl, 131 + ), 132 + [slingshotBaseUrl], 133 + ); 134 + const normalizedAppview = useMemo( 135 + () => 136 + normalizeBaseUrl( 137 + blueskyAppviewService && blueskyAppviewService.trim() 138 + ? blueskyAppviewService 139 + : DEFAULT_CONFIG.blueskyAppviewService, 140 + ), 141 + [blueskyAppviewService], 142 + ); 143 + const normalizedBlueskyApp = useMemo( 144 + () => 145 + normalizeBaseUrl( 146 + blueskyAppBaseUrl && blueskyAppBaseUrl.trim() 147 + ? blueskyAppBaseUrl 148 + : DEFAULT_CONFIG.blueskyAppBaseUrl, 149 + ), 150 + [blueskyAppBaseUrl], 151 + ); 152 + const normalizedTangled = useMemo( 153 + () => 154 + normalizeBaseUrl( 155 + tangledBaseUrl && tangledBaseUrl.trim() 156 + ? tangledBaseUrl 157 + : DEFAULT_CONFIG.tangledBaseUrl, 158 + ), 159 + [tangledBaseUrl], 160 + ); 161 + const normalizedConstellation = useMemo( 162 + () => 163 + normalizeBaseUrl( 164 + constellationBaseUrl && constellationBaseUrl.trim() 165 + ? constellationBaseUrl 166 + : DEFAULT_CONFIG.constellationBaseUrl, 167 + ), 168 + [constellationBaseUrl], 169 + ); 170 const resolver = useMemo( 171 + () => new ServiceResolver({ 172 + plcDirectory: normalizedPlc, 173 + identityService: normalizedIdentity, 174 + slingshotBaseUrl: normalizedSlingshot, 175 + }), 176 + [normalizedPlc, normalizedIdentity, normalizedSlingshot], 177 ); 178 const cachesRef = useRef<{ 179 didCache: DidCache; 180 blobCache: BlobCache; 181 + recordCache: RecordCache; 182 } | null>(null); 183 if (!cachesRef.current) { 184 cachesRef.current = { 185 didCache: new DidCache(), 186 blobCache: new BlobCache(), 187 + recordCache: new RecordCache(), 188 }; 189 } 190 ··· 192 () => ({ 193 resolver, 194 plcDirectory: normalizedPlc, 195 + blueskyAppviewService: normalizedAppview, 196 + blueskyAppBaseUrl: normalizedBlueskyApp, 197 + tangledBaseUrl: normalizedTangled, 198 + constellationBaseUrl: normalizedConstellation, 199 didCache: cachesRef.current!.didCache, 200 blobCache: cachesRef.current!.blobCache, 201 + recordCache: cachesRef.current!.recordCache, 202 }), 203 + [resolver, normalizedPlc, normalizedAppview, normalizedBlueskyApp, normalizedTangled, normalizedConstellation], 204 ); 205 206 return ( ··· 213 /** 214 * Hook that accesses the AT Protocol context provided by `AtProtoProvider`. 215 * 216 + * This hook exposes the service resolver, DID cache, blob cache, and record cache 217 + * for building custom AT Protocol functionality. 218 * 219 * @throws {Error} When called outside of an `AtProtoProvider`. 220 * @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL. ··· 224 * import { useAtProto } from 'atproto-ui'; 225 * 226 * function MyCustomComponent() { 227 + * const { resolver, didCache, blobCache, recordCache } = useAtProto(); 228 * // Use the resolver and caches for custom AT Protocol operations 229 * } 230 * ```
+43 -8
lib/renderers/BlueskyPostRenderer.tsx
··· 56 57 if (error) { 58 return ( 59 - <div style={{ padding: 8, color: "crimson" }}> 60 Failed to load post. 61 </div> 62 ); 63 } 64 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 65 66 const text = record.text; 67 const createdDate = new Date(record.createdAt); ··· 181 </div> 182 ); 183 184 - const Avatar: React.FC<{ avatarUrl?: string }> = ({ avatarUrl }) => 185 avatarUrl ? ( 186 - <img src={avatarUrl} alt="avatar" style={baseStyles.avatarImg} /> 187 ) : ( 188 - <div style={baseStyles.avatarPlaceholder} aria-hidden /> 189 ); 190 191 const ReplyInfo: React.FC<{ ··· 278 279 const ThreadLayout: React.FC<LayoutProps> = (props) => ( 280 <div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}> 281 - <Avatar avatarUrl={props.avatarUrl} /> 282 <div style={{ flex: 1, minWidth: 0 }}> 283 <div 284 style={{ ··· 326 const DefaultLayout: React.FC<LayoutProps> = (props) => ( 327 <> 328 <header style={baseStyles.header}> 329 - <Avatar avatarUrl={props.avatarUrl} /> 330 <AuthorInfo 331 primaryName={props.primaryName} 332 authorDisplayName={props.authorDisplayName} ··· 531 } 532 533 const PostImage: React.FC<PostImageProps> = ({ image, did }) => { 534 const imageBlob = image.image; 535 const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined; 536 const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob); 537 const { url: urlFromBlob, loading, error } = useBlob(did, cid); 538 const url = cdnUrl || urlFromBlob; 539 const alt = image.alt?.trim() || "Bluesky attachment"; 540 541 const aspect = 542 image.aspectRatio && image.aspectRatio.height > 0 ··· 561 <img src={url} alt={alt} style={imagesBase.img} /> 562 ) : ( 563 <div 564 style={{ 565 ...imagesBase.placeholder, 566 color: `var(--atproto-color-text-muted)`, ··· 573 : "Image unavailable"} 574 </div> 575 )} 576 </div> 577 - {image.alt && image.alt.trim().length > 0 && ( 578 <figcaption 579 style={{ 580 ...imagesBase.caption, ··· 621 caption: { 622 fontSize: 12, 623 lineHeight: 1.3, 624 } satisfies React.CSSProperties, 625 }; 626
··· 56 57 if (error) { 58 return ( 59 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 60 Failed to load post. 61 </div> 62 ); 63 } 64 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 65 66 const text = record.text; 67 const createdDate = new Date(record.createdAt); ··· 181 </div> 182 ); 183 184 + const Avatar: React.FC<{ avatarUrl?: string; name?: string }> = ({ avatarUrl, name }) => 185 avatarUrl ? ( 186 + <img src={avatarUrl} alt={`${name || 'User'}'s profile picture`} style={baseStyles.avatarImg} /> 187 ) : ( 188 + <div style={baseStyles.avatarPlaceholder} aria-hidden="true" /> 189 ); 190 191 const ReplyInfo: React.FC<{ ··· 278 279 const ThreadLayout: React.FC<LayoutProps> = (props) => ( 280 <div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}> 281 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 282 <div style={{ flex: 1, minWidth: 0 }}> 283 <div 284 style={{ ··· 326 const DefaultLayout: React.FC<LayoutProps> = (props) => ( 327 <> 328 <header style={baseStyles.header}> 329 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 330 <AuthorInfo 331 primaryName={props.primaryName} 332 authorDisplayName={props.authorDisplayName} ··· 531 } 532 533 const PostImage: React.FC<PostImageProps> = ({ image, did }) => { 534 + const [showAltText, setShowAltText] = React.useState(false); 535 const imageBlob = image.image; 536 const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined; 537 const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob); 538 const { url: urlFromBlob, loading, error } = useBlob(did, cid); 539 const url = cdnUrl || urlFromBlob; 540 const alt = image.alt?.trim() || "Bluesky attachment"; 541 + const hasAlt = image.alt && image.alt.trim().length > 0; 542 543 const aspect = 544 image.aspectRatio && image.aspectRatio.height > 0 ··· 563 <img src={url} alt={alt} style={imagesBase.img} /> 564 ) : ( 565 <div 566 + role={error ? "alert" : "status"} 567 style={{ 568 ...imagesBase.placeholder, 569 color: `var(--atproto-color-text-muted)`, ··· 576 : "Image unavailable"} 577 </div> 578 )} 579 + {hasAlt && ( 580 + <button 581 + onClick={() => setShowAltText(!showAltText)} 582 + style={{ 583 + ...imagesBase.altBadge, 584 + background: showAltText 585 + ? `var(--atproto-color-text)` 586 + : `var(--atproto-color-bg-secondary)`, 587 + color: showAltText 588 + ? `var(--atproto-color-bg)` 589 + : `var(--atproto-color-text)`, 590 + }} 591 + title="Toggle alt text" 592 + aria-label="Toggle alt text" 593 + > 594 + ALT 595 + </button> 596 + )} 597 </div> 598 + {hasAlt && showAltText && ( 599 <figcaption 600 style={{ 601 ...imagesBase.caption, ··· 642 caption: { 643 fontSize: 12, 644 lineHeight: 1.3, 645 + } satisfies React.CSSProperties, 646 + altBadge: { 647 + position: "absolute", 648 + bottom: 8, 649 + right: 8, 650 + padding: "4px 8px", 651 + fontSize: 10, 652 + fontWeight: 600, 653 + letterSpacing: "0.5px", 654 + border: "none", 655 + borderRadius: 4, 656 + cursor: "pointer", 657 + transition: "background 150ms ease, color 150ms ease", 658 + fontFamily: "system-ui, sans-serif", 659 } satisfies React.CSSProperties, 660 }; 661
+47 -36
lib/renderers/BlueskyProfileRenderer.tsx
··· 1 import React from "react"; 2 import type { ProfileRecord } from "../types/bluesky"; 3 import { BlueskyIcon } from "../components/BlueskyIcon"; 4 5 export interface BlueskyProfileRendererProps { 6 record: ProfileRecord; ··· 19 handle, 20 avatarUrl, 21 }) => { 22 23 if (error) 24 return ( 25 - <div style={{ padding: 8, color: "crimson" }}> 26 Failed to load profile. 27 </div> 28 ); 29 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 30 31 - const profileUrl = `https://bsky.app/profile/${did}`; 32 const rawWebsite = record.website?.trim(); 33 const websiteHref = rawWebsite 34 ? rawWebsite.match(/^https?:\/\//i) ··· 43 <div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 44 <div style={base.header}> 45 {avatarUrl ? ( 46 - <img src={avatarUrl} alt="avatar" style={base.avatarImg} /> 47 ) : ( 48 <div 49 style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }} 50 - aria-label="avatar" 51 /> 52 )} 53 <div style={{ flex: 1 }}> ··· 69 {record.description} 70 </p> 71 )} 72 - {record.createdAt && ( 73 - <div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}> 74 - Joined {new Date(record.createdAt).toLocaleDateString()} 75 - </div> 76 - )} 77 - <div style={base.links}> 78 - {websiteHref && websiteLabel && ( 79 <a 80 - href={websiteHref} 81 target="_blank" 82 rel="noopener noreferrer" 83 style={{ ...base.link, color: `var(--atproto-color-link)` }} 84 > 85 - {websiteLabel} 86 </a> 87 - )} 88 - <a 89 - href={profileUrl} 90 - target="_blank" 91 - rel="noopener noreferrer" 92 - style={{ ...base.link, color: `var(--atproto-color-link)` }} 93 - > 94 - View on Bluesky 95 - </a> 96 - </div> 97 - <div style={base.iconCorner} aria-hidden> 98 - <BlueskyIcon size={18} /> 99 </div> 100 </div> 101 ); ··· 103 104 const base: Record<string, React.CSSProperties> = { 105 card: { 106 borderRadius: 12, 107 padding: 16, 108 fontFamily: "system-ui, sans-serif", ··· 140 lineHeight: 1.4, 141 }, 142 meta: { 143 - marginTop: 12, 144 fontSize: 12, 145 }, 146 pronouns: { ··· 153 padding: "2px 8px", 154 marginTop: 6, 155 }, 156 - links: { 157 - display: "flex", 158 - flexDirection: "column", 159 - gap: 8, 160 - marginTop: 12, 161 - }, 162 link: { 163 display: "inline-flex", 164 alignItems: "center", ··· 167 fontWeight: 600, 168 textDecoration: "none", 169 }, 170 iconCorner: { 171 - position: "absolute", 172 - right: 12, 173 - bottom: 12, 174 }, 175 }; 176
··· 1 import React from "react"; 2 import type { ProfileRecord } from "../types/bluesky"; 3 import { BlueskyIcon } from "../components/BlueskyIcon"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 6 export interface BlueskyProfileRendererProps { 7 record: ProfileRecord; ··· 20 handle, 21 avatarUrl, 22 }) => { 23 + const { blueskyAppBaseUrl } = useAtProto(); 24 25 if (error) 26 return ( 27 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 28 Failed to load profile. 29 </div> 30 ); 31 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 32 33 + const profileUrl = `${blueskyAppBaseUrl}/profile/${did}`; 34 const rawWebsite = record.website?.trim(); 35 const websiteHref = rawWebsite 36 ? rawWebsite.match(/^https?:\/\//i) ··· 45 <div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 46 <div style={base.header}> 47 {avatarUrl ? ( 48 + <img src={avatarUrl} alt={`${record.displayName || handle || did}'s profile picture`} style={base.avatarImg} /> 49 ) : ( 50 <div 51 style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }} 52 + aria-hidden="true" 53 /> 54 )} 55 <div style={{ flex: 1 }}> ··· 71 {record.description} 72 </p> 73 )} 74 + <div style={base.bottomRow}> 75 + <div style={base.bottomLeft}> 76 + {record.createdAt && ( 77 + <div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}> 78 + Joined {new Date(record.createdAt).toLocaleDateString()} 79 + </div> 80 + )} 81 + {websiteHref && websiteLabel && ( 82 + <a 83 + href={websiteHref} 84 + target="_blank" 85 + rel="noopener noreferrer" 86 + style={{ ...base.link, color: `var(--atproto-color-link)` }} 87 + > 88 + {websiteLabel} 89 + </a> 90 + )} 91 <a 92 + href={profileUrl} 93 target="_blank" 94 rel="noopener noreferrer" 95 style={{ ...base.link, color: `var(--atproto-color-link)` }} 96 > 97 + View on Bluesky 98 </a> 99 + </div> 100 + <div aria-hidden> 101 + <BlueskyIcon size={18} /> 102 + </div> 103 </div> 104 </div> 105 ); ··· 107 108 const base: Record<string, React.CSSProperties> = { 109 card: { 110 + display: "flex", 111 + flexDirection: "column", 112 + height: "100%", 113 borderRadius: 12, 114 padding: 16, 115 fontFamily: "system-ui, sans-serif", ··· 147 lineHeight: 1.4, 148 }, 149 meta: { 150 + marginTop: 0, 151 fontSize: 12, 152 }, 153 pronouns: { ··· 160 padding: "2px 8px", 161 marginTop: 6, 162 }, 163 link: { 164 display: "inline-flex", 165 alignItems: "center", ··· 168 fontWeight: 600, 169 textDecoration: "none", 170 }, 171 + bottomRow: { 172 + display: "flex", 173 + alignItems: "flex-end", 174 + justifyContent: "space-between", 175 + marginTop: "auto", 176 + paddingTop: 12, 177 + }, 178 + bottomLeft: { 179 + display: "flex", 180 + flexDirection: "column", 181 + gap: 8, 182 + }, 183 iconCorner: { 184 + // Removed absolute positioning, now in flex layout 185 }, 186 }; 187
+749
lib/renderers/CurrentlyPlayingRenderer.tsx
···
··· 1 + import React, { useState, useEffect, useRef } from "react"; 2 + import type { TealActorStatusRecord } from "../types/teal"; 3 + 4 + export interface CurrentlyPlayingRendererProps { 5 + record: TealActorStatusRecord; 6 + error?: Error; 7 + loading: boolean; 8 + did: string; 9 + rkey: string; 10 + colorScheme?: "light" | "dark" | "system"; 11 + /** Label to display (e.g., "CURRENTLY PLAYING", "LAST PLAYED"). Defaults to "CURRENTLY PLAYING". */ 12 + label?: string; 13 + /** Handle to display in not listening state */ 14 + handle?: string; 15 + } 16 + 17 + interface SonglinkPlatform { 18 + url: string; 19 + entityUniqueId: string; 20 + nativeAppUriMobile?: string; 21 + nativeAppUriDesktop?: string; 22 + } 23 + 24 + interface SonglinkResponse { 25 + linksByPlatform: { 26 + [platform: string]: SonglinkPlatform; 27 + }; 28 + entitiesByUniqueId: { 29 + [id: string]: { 30 + thumbnailUrl?: string; 31 + title?: string; 32 + artistName?: string; 33 + }; 34 + }; 35 + } 36 + 37 + export const CurrentlyPlayingRenderer: React.FC<CurrentlyPlayingRendererProps> = ({ 38 + record, 39 + error, 40 + loading, 41 + label = "CURRENTLY PLAYING", 42 + handle, 43 + }) => { 44 + const [albumArt, setAlbumArt] = useState<string | undefined>(undefined); 45 + const [artworkLoading, setArtworkLoading] = useState(true); 46 + const [songlinkData, setSonglinkData] = useState<SonglinkResponse | undefined>(undefined); 47 + const [showPlatformModal, setShowPlatformModal] = useState(false); 48 + const previousTrackIdentityRef = useRef<string>(""); 49 + 50 + // Auto-refresh interval removed - handled by AtProtoRecord 51 + 52 + useEffect(() => { 53 + if (!record) return; 54 + 55 + const { item } = record; 56 + const artistName = item.artists[0]?.artistName; 57 + const trackName = item.trackName; 58 + 59 + if (!artistName || !trackName) { 60 + setArtworkLoading(false); 61 + return; 62 + } 63 + 64 + // Create a unique identity for this track 65 + const trackIdentity = `${trackName}::${artistName}`; 66 + 67 + // Check if the track has actually changed 68 + const trackHasChanged = trackIdentity !== previousTrackIdentityRef.current; 69 + 70 + // Update tracked identity 71 + previousTrackIdentityRef.current = trackIdentity; 72 + 73 + // Only reset loading state and clear data when track actually changes 74 + // This prevents the loading flicker when auto-refreshing the same track 75 + if (trackHasChanged) { 76 + console.log(`[teal.fm] ๐ŸŽต Track changed: "${trackName}" by ${artistName}`); 77 + setArtworkLoading(true); 78 + setAlbumArt(undefined); 79 + setSonglinkData(undefined); 80 + } else { 81 + console.log(`[teal.fm] ๐Ÿ”„ Auto-refresh: same track still playing ("${trackName}" by ${artistName})`); 82 + } 83 + 84 + let cancelled = false; 85 + 86 + const fetchMusicData = async () => { 87 + try { 88 + // Step 1: Check if we have an ISRC - Songlink supports this directly 89 + if (item.isrc) { 90 + console.log(`[teal.fm] Attempting ISRC lookup for ${trackName} by ${artistName}`, { isrc: item.isrc }); 91 + const response = await fetch( 92 + `https://api.song.link/v1-alpha.1/links?platform=isrc&type=song&id=${encodeURIComponent(item.isrc)}&songIfSingle=true` 93 + ); 94 + if (cancelled) return; 95 + if (response.ok) { 96 + const data = await response.json(); 97 + setSonglinkData(data); 98 + 99 + // Extract album art from Songlink data 100 + const entityId = data.entityUniqueId; 101 + const entity = data.entitiesByUniqueId?.[entityId]; 102 + 103 + // Debug: Log the entity structure to see what fields are available 104 + console.log(`[teal.fm] ISRC entity data:`, { entityId, entity }); 105 + 106 + if (entity?.thumbnailUrl) { 107 + console.log(`[teal.fm] โœ“ Found album art via ISRC lookup`); 108 + setAlbumArt(entity.thumbnailUrl); 109 + } else { 110 + console.warn(`[teal.fm] ISRC lookup succeeded but no thumbnail found`, { 111 + entityId, 112 + entityKeys: entity ? Object.keys(entity) : 'no entity', 113 + entity 114 + }); 115 + } 116 + setArtworkLoading(false); 117 + return; 118 + } else { 119 + console.warn(`[teal.fm] ISRC lookup failed with status ${response.status}`); 120 + } 121 + } 122 + 123 + // Step 2: Search iTunes Search API to find the track (single request for both artwork and links) 124 + console.log(`[teal.fm] Attempting iTunes search for: "${trackName}" by "${artistName}"`); 125 + const iTunesSearchUrl = `https://itunes.apple.com/search?term=${encodeURIComponent( 126 + `${trackName} ${artistName}` 127 + )}&media=music&entity=song&limit=1`; 128 + 129 + const iTunesResponse = await fetch(iTunesSearchUrl); 130 + 131 + if (cancelled) return; 132 + 133 + if (iTunesResponse.ok) { 134 + const iTunesData = await iTunesResponse.json(); 135 + 136 + if (iTunesData.results && iTunesData.results.length > 0) { 137 + const match = iTunesData.results[0]; 138 + const iTunesId = match.trackId; 139 + 140 + // Set album artwork immediately (600x600 for high quality) 141 + const artworkUrl = match.artworkUrl100?.replace('100x100', '600x600') || match.artworkUrl100; 142 + if (artworkUrl) { 143 + console.log(`[teal.fm] โœ“ Found album art via iTunes search`, { url: artworkUrl }); 144 + setAlbumArt(artworkUrl); 145 + } else { 146 + console.warn(`[teal.fm] iTunes match found but no artwork URL`); 147 + } 148 + setArtworkLoading(false); 149 + 150 + // Step 3: Use iTunes ID with Songlink to get all platform links 151 + console.log(`[teal.fm] Fetching platform links via Songlink (iTunes ID: ${iTunesId})`); 152 + const songlinkResponse = await fetch( 153 + `https://api.song.link/v1-alpha.1/links?platform=itunes&type=song&id=${iTunesId}&songIfSingle=true` 154 + ); 155 + 156 + if (cancelled) return; 157 + 158 + if (songlinkResponse.ok) { 159 + const songlinkData = await songlinkResponse.json(); 160 + console.log(`[teal.fm] โœ“ Got platform links from Songlink`); 161 + setSonglinkData(songlinkData); 162 + return; 163 + } else { 164 + console.warn(`[teal.fm] Songlink request failed with status ${songlinkResponse.status}`); 165 + } 166 + } else { 167 + console.warn(`[teal.fm] No iTunes results found for "${trackName}" by "${artistName}"`); 168 + setArtworkLoading(false); 169 + } 170 + } else { 171 + console.warn(`[teal.fm] iTunes search failed with status ${iTunesResponse.status}`); 172 + } 173 + 174 + // Step 4: Fallback - if originUrl is from a supported platform, try it directly 175 + if (item.originUrl && ( 176 + item.originUrl.includes('spotify.com') || 177 + item.originUrl.includes('apple.com') || 178 + item.originUrl.includes('youtube.com') || 179 + item.originUrl.includes('tidal.com') 180 + )) { 181 + console.log(`[teal.fm] Attempting Songlink lookup via originUrl`, { url: item.originUrl }); 182 + const songlinkResponse = await fetch( 183 + `https://api.song.link/v1-alpha.1/links?url=${encodeURIComponent(item.originUrl)}&songIfSingle=true` 184 + ); 185 + 186 + if (cancelled) return; 187 + 188 + if (songlinkResponse.ok) { 189 + const data = await songlinkResponse.json(); 190 + console.log(`[teal.fm] โœ“ Got data from Songlink via originUrl`); 191 + setSonglinkData(data); 192 + 193 + // Try to get artwork from Songlink if we don't have it yet 194 + if (!albumArt) { 195 + const entityId = data.entityUniqueId; 196 + const entity = data.entitiesByUniqueId?.[entityId]; 197 + 198 + // Debug: Log the entity structure to see what fields are available 199 + console.log(`[teal.fm] Songlink originUrl entity data:`, { entityId, entity }); 200 + 201 + if (entity?.thumbnailUrl) { 202 + console.log(`[teal.fm] โœ“ Found album art via Songlink originUrl lookup`); 203 + setAlbumArt(entity.thumbnailUrl); 204 + } else { 205 + console.warn(`[teal.fm] Songlink lookup succeeded but no thumbnail found`, { 206 + entityId, 207 + entityKeys: entity ? Object.keys(entity) : 'no entity', 208 + entity 209 + }); 210 + } 211 + } 212 + } else { 213 + console.warn(`[teal.fm] Songlink originUrl lookup failed with status ${songlinkResponse.status}`); 214 + } 215 + } 216 + 217 + if (!albumArt) { 218 + console.warn(`[teal.fm] โœ— All album art fetch methods failed for "${trackName}" by "${artistName}"`); 219 + } 220 + 221 + setArtworkLoading(false); 222 + } catch (err) { 223 + console.error(`[teal.fm] โœ— Error fetching music data for "${trackName}" by "${artistName}":`, err); 224 + setArtworkLoading(false); 225 + } 226 + }; 227 + 228 + fetchMusicData(); 229 + 230 + return () => { 231 + cancelled = true; 232 + }; 233 + }, [record]); // Runs on record change 234 + 235 + if (error) 236 + return ( 237 + <div role="alert" style={{ padding: 8, color: "var(--atproto-color-error)" }}> 238 + Failed to load status. 239 + </div> 240 + ); 241 + if (loading && !record) 242 + return ( 243 + <div role="status" aria-live="polite" style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 244 + Loadingโ€ฆ 245 + </div> 246 + ); 247 + 248 + const { item } = record; 249 + 250 + // Check if user is not listening to anything 251 + const isNotListening = !item.trackName || item.artists.length === 0; 252 + 253 + // Show "not listening" state 254 + if (isNotListening) { 255 + const displayHandle = handle || "User"; 256 + return ( 257 + <div style={styles.notListeningContainer}> 258 + <div style={styles.notListeningIcon}> 259 + <svg 260 + width="80" 261 + height="80" 262 + viewBox="0 0 24 24" 263 + fill="none" 264 + stroke="currentColor" 265 + strokeWidth="1.5" 266 + strokeLinecap="round" 267 + strokeLinejoin="round" 268 + > 269 + <path d="M9 18V5l12-2v13" /> 270 + <circle cx="6" cy="18" r="3" /> 271 + <circle cx="18" cy="16" r="3" /> 272 + </svg> 273 + </div> 274 + <div style={styles.notListeningTitle}> 275 + {displayHandle} isn't listening to anything 276 + </div> 277 + <div style={styles.notListeningSubtitle}>Check back soon</div> 278 + </div> 279 + ); 280 + } 281 + 282 + const artistNames = item.artists.map((a) => a.artistName).join(", "); 283 + 284 + const platformConfig: Record<string, { name: string; svg: string; color: string }> = { 285 + spotify: { 286 + name: "Spotify", 287 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="#1ed760" d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8Z"/><path d="M406.6 231.1c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3zm-31 76.2c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm-26.9 65.6c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4z"/></svg>', 288 + color: "#1DB954" 289 + }, 290 + appleMusic: { 291 + name: "Apple Music", 292 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 361 361"><defs><linearGradient id="apple-grad" x1="180" y1="358.6" x2="180" y2="7.76" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#FA233B"/><stop offset="1" style="stop-color:#FB5C74"/></linearGradient></defs><path fill="url(#apple-grad)" d="M360 112.61V247.39c0 4.3 0 8.6-.02 12.9-.02 3.62-.06 7.24-.16 10.86-.21 7.89-.68 15.84-2.08 23.64-1.42 7.92-3.75 15.29-7.41 22.49-3.6 7.07-8.3 13.53-13.91 19.14-5.61 5.61-12.08 10.31-19.15 13.91-7.19 3.66-14.56 5.98-22.47 7.41-7.8 1.4-15.76 1.87-23.65 2.08-3.62.1-7.24.14-10.86.16-4.3.03-8.6.02-12.9.02H112.61c-4.3 0-8.6 0-12.9-.02-3.62-.02-7.24-.06-10.86-.16-7.89-.21-15.85-.68-23.65-2.08-7.92-1.42-15.28-3.75-22.47-7.41-7.07-3.6-13.54-8.3-19.15-13.91-5.61-5.61-10.31-12.07-13.91-19.14-3.66-7.2-5.99-14.57-7.41-22.49-1.4-7.8-1.87-15.76-2.08-23.64-.1-3.62-.14-7.24-.16-10.86C0 255.99 0 251.69 0 247.39V112.61c0-4.3 0-8.6.02-12.9.02-3.62.06-7.24.16-10.86.21-7.89.68-15.84 2.08-23.64 1.42-7.92 3.75-15.29 7.41-22.49 3.6-7.07 8.3-13.53 13.91-19.14 5.61-5.61 12.08-10.31 19.15-13.91 7.19-3.66 14.56-5.98 22.47-7.41 7.8-1.4 15.76-1.87 23.65-2.08 3.62-.1 7.24-.14 10.86-.16C104.01 0 108.31 0 112.61 0h134.77c4.3 0 8.6 0 12.9.02 3.62.02 7.24.06 10.86.16 7.89.21 15.85.68 23.65 2.08 7.92 1.42 15.28 3.75 22.47 7.41 7.07 3.6 13.54 8.3 19.15 13.91 5.61 5.61 10.31 12.07 13.91 19.14 3.66 7.2 5.99 14.57 7.41 22.49 1.4 7.8 1.87 15.76 2.08 23.64.1 3.62.14 7.24.16 10.86.03 4.3.02 8.6.02 12.9z"/><path fill="#FFF" d="M254.5 55c-.87.08-8.6 1.45-9.53 1.64l-107 21.59-.04.01c-2.79.59-4.98 1.58-6.67 3-2.04 1.71-3.17 4.13-3.6 6.95-.09.6-.24 1.82-.24 3.62v133.92c0 3.13-.25 6.17-2.37 8.76-2.12 2.59-4.74 3.37-7.81 3.99-2.33.47-4.66.94-6.99 1.41-8.84 1.78-14.59 2.99-19.8 5.01-4.98 1.93-8.71 4.39-11.68 7.51-5.89 6.17-8.28 14.54-7.46 22.38.7 6.69 3.71 13.09 8.88 17.82 3.49 3.2 7.85 5.63 12.99 6.66 5.33 1.07 11.01.7 19.31-.98 4.42-.89 8.56-2.28 12.5-4.61 3.9-2.3 7.24-5.37 9.85-9.11 2.62-3.75 4.31-7.92 5.24-12.35.96-4.57 1.19-8.7 1.19-13.26V128.82c0-6.22 1.76-7.86 6.78-9.08l93.09-18.75c5.79-1.11 8.52.54 8.52 6.61v79.29c0 3.14-.03 6.32-2.17 8.92-2.12 2.59-4.74 3.37-7.81 3.99-2.33.47-4.66.94-6.99 1.41-8.84 1.78-14.59 2.99-19.8 5.01-4.98 1.93-8.71 4.39-11.68 7.51-5.89 6.17-8.49 14.54-7.67 22.38.7 6.69 3.92 13.09 9.09 17.82 3.49 3.2 7.85 5.56 12.99 6.6 5.33 1.07 11.01.69 19.31-.98 4.42-.89 8.56-2.22 12.5-4.55 3.9-2.3 7.24-5.37 9.85-9.11 2.62-3.75 4.31-7.92 5.24-12.35.96-4.57 1-8.7 1-13.26V64.46c0-6.16-3.25-9.96-9.04-9.46z"/></svg>', 293 + color: "#FA243C" 294 + }, 295 + youtube: { 296 + name: "YouTube", 297 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><g transform="scale(.75)"><path fill="red" d="M199.917 105.63s-84.292 0-105.448 5.497c-11.328 3.165-20.655 12.493-23.82 23.987-5.498 21.156-5.498 64.969-5.498 64.969s0 43.979 5.497 64.802c3.165 11.494 12.326 20.655 23.82 23.82 21.323 5.664 105.448 5.664 105.448 5.664s84.459 0 105.615-5.497c11.494-3.165 20.655-12.16 23.654-23.82 5.664-20.99 5.664-64.803 5.664-64.803s.166-43.98-5.664-65.135c-2.999-11.494-12.16-20.655-23.654-23.654-21.156-5.83-105.615-5.83-105.615-5.83zm-26.82 53.974 70.133 40.479-70.133 40.312v-80.79z"/><path fill="#fff" d="m173.097 159.604 70.133 40.479-70.133 40.312v-80.79z"/></g></svg>', 298 + color: "#FF0000" 299 + }, 300 + youtubeMusic: { 301 + name: "YouTube Music", 302 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 176"><circle fill="#FF0000" cx="88" cy="88" r="88"/><path fill="#FFF" d="M88 46c23.1 0 42 18.8 42 42s-18.8 42-42 42-42-18.8-42-42 18.8-42 42-42m0-4c-25.4 0-46 20.6-46 46s20.6 46 46 46 46-20.6 46-46-20.6-46-46-46z"/><path fill="#FFF" d="m72 111 39-24-39-22z"/></svg>', 303 + color: "#FF0000" 304 + }, 305 + tidal: { 306 + name: "Tidal", 307 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0c141.385 0 256 114.615 256 256S397.385 512 256 512 0 397.385 0 256 114.615 0 256 0zm50.384 219.459-50.372 50.383 50.379 50.391-50.382 50.393-50.395-50.393 50.393-50.389-50.393-50.39 50.395-50.372 50.38 50.369 50.389-50.375 50.382 50.382-50.382 50.392-50.394-50.391zm-100.767-.001-50.392 50.392-50.385-50.392 50.385-50.382 50.392 50.382z"/></svg>', 308 + color: "#000000" 309 + }, 310 + bandcamp: { 311 + name: "Bandcamp", 312 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#1DA0C3" d="M0 156v200h172l84-200z"/></svg>', 313 + color: "#1DA0C3" 314 + }, 315 + }; 316 + 317 + const availablePlatforms = songlinkData 318 + ? Object.keys(platformConfig).filter((platform) => 319 + songlinkData.linksByPlatform[platform] 320 + ) 321 + : []; 322 + 323 + return ( 324 + <> 325 + <div style={styles.container}> 326 + {/* Album Artwork */} 327 + <div style={styles.artworkContainer}> 328 + {artworkLoading ? ( 329 + <div style={styles.artworkPlaceholder}> 330 + <div style={styles.loadingSpinner} /> 331 + </div> 332 + ) : albumArt ? ( 333 + <img 334 + src={albumArt} 335 + alt={`${item.releaseName || "Album"} cover`} 336 + style={styles.artwork} 337 + onError={(e) => { 338 + console.error("Failed to load album art:", { 339 + url: albumArt, 340 + track: item.trackName, 341 + artist: item.artists[0]?.artistName, 342 + error: "Image load error" 343 + }); 344 + e.currentTarget.style.display = "none"; 345 + }} 346 + /> 347 + ) : ( 348 + <div style={styles.artworkPlaceholder}> 349 + <svg 350 + width="64" 351 + height="64" 352 + viewBox="0 0 24 24" 353 + fill="none" 354 + stroke="currentColor" 355 + strokeWidth="1.5" 356 + > 357 + <circle cx="12" cy="12" r="10" /> 358 + <circle cx="12" cy="12" r="3" /> 359 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 360 + </svg> 361 + </div> 362 + )} 363 + </div> 364 + 365 + {/* Content */} 366 + <div style={styles.content}> 367 + <div style={styles.label}>{label}</div> 368 + <h2 style={styles.trackName}>{item.trackName}</h2> 369 + <div style={styles.artistName}>{artistNames}</div> 370 + {item.releaseName && ( 371 + <div style={styles.releaseName}>from {item.releaseName}</div> 372 + )} 373 + 374 + {/* Listen Button */} 375 + {availablePlatforms.length > 0 ? ( 376 + <button 377 + onClick={() => setShowPlatformModal(true)} 378 + style={styles.listenButton} 379 + data-teal-listen-button="true" 380 + > 381 + <span>Listen with your Streaming Client</span> 382 + <svg 383 + width="16" 384 + height="16" 385 + viewBox="0 0 24 24" 386 + fill="none" 387 + stroke="currentColor" 388 + strokeWidth="2" 389 + strokeLinecap="round" 390 + strokeLinejoin="round" 391 + > 392 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 393 + <polyline points="15 3 21 3 21 9" /> 394 + <line x1="10" y1="14" x2="21" y2="3" /> 395 + </svg> 396 + </button> 397 + ) : item.originUrl ? ( 398 + <a 399 + href={item.originUrl} 400 + target="_blank" 401 + rel="noopener noreferrer" 402 + style={styles.listenButton} 403 + data-teal-listen-button="true" 404 + > 405 + <span>Listen on Last.fm</span> 406 + <svg 407 + width="16" 408 + height="16" 409 + viewBox="0 0 24 24" 410 + fill="none" 411 + stroke="currentColor" 412 + strokeWidth="2" 413 + strokeLinecap="round" 414 + strokeLinejoin="round" 415 + > 416 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 417 + <polyline points="15 3 21 3 21 9" /> 418 + <line x1="10" y1="14" x2="21" y2="3" /> 419 + </svg> 420 + </a> 421 + ) : null} 422 + </div> 423 + </div> 424 + 425 + {/* Platform Selection Modal */} 426 + {showPlatformModal && songlinkData && ( 427 + <div style={styles.modalOverlay} onClick={() => setShowPlatformModal(false)}> 428 + <div 429 + role="dialog" 430 + aria-modal="true" 431 + aria-labelledby="platform-modal-title" 432 + style={styles.modalContent} 433 + onClick={(e) => e.stopPropagation()} 434 + > 435 + <div style={styles.modalHeader}> 436 + <h3 id="platform-modal-title" style={styles.modalTitle}>Choose your streaming service</h3> 437 + <button 438 + style={styles.closeButton} 439 + onClick={() => setShowPlatformModal(false)} 440 + data-teal-close="true" 441 + > 442 + ร— 443 + </button> 444 + </div> 445 + <div style={styles.platformList}> 446 + {availablePlatforms.map((platform) => { 447 + const config = platformConfig[platform]; 448 + const link = songlinkData.linksByPlatform[platform]; 449 + return ( 450 + <a 451 + key={platform} 452 + href={link.url} 453 + target="_blank" 454 + rel="noopener noreferrer" 455 + style={{ 456 + ...styles.platformItem, 457 + borderLeft: `4px solid ${config.color}`, 458 + }} 459 + onClick={() => setShowPlatformModal(false)} 460 + data-teal-platform="true" 461 + > 462 + <span 463 + style={styles.platformIcon} 464 + dangerouslySetInnerHTML={{ __html: config.svg }} 465 + /> 466 + <span style={styles.platformName}>{config.name}</span> 467 + <svg 468 + width="20" 469 + height="20" 470 + viewBox="0 0 24 24" 471 + fill="none" 472 + stroke="currentColor" 473 + strokeWidth="2" 474 + style={styles.platformArrow} 475 + > 476 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 477 + <polyline points="15 3 21 3 21 9" /> 478 + <line x1="10" y1="14" x2="21" y2="3" /> 479 + </svg> 480 + </a> 481 + ); 482 + })} 483 + </div> 484 + </div> 485 + </div> 486 + )} 487 + </> 488 + ); 489 + }; 490 + 491 + const styles: Record<string, React.CSSProperties> = { 492 + container: { 493 + fontFamily: "system-ui, -apple-system, sans-serif", 494 + display: "flex", 495 + flexDirection: "column", 496 + background: "var(--atproto-color-bg)", 497 + borderRadius: 16, 498 + overflow: "hidden", 499 + maxWidth: 420, 500 + color: "var(--atproto-color-text)", 501 + boxShadow: "0 8px 24px rgba(0, 0, 0, 0.4)", 502 + border: "1px solid var(--atproto-color-border)", 503 + }, 504 + artworkContainer: { 505 + width: "100%", 506 + aspectRatio: "1 / 1", 507 + position: "relative", 508 + overflow: "hidden", 509 + }, 510 + artwork: { 511 + width: "100%", 512 + height: "100%", 513 + objectFit: "cover", 514 + display: "block", 515 + }, 516 + artworkPlaceholder: { 517 + width: "100%", 518 + height: "100%", 519 + display: "flex", 520 + alignItems: "center", 521 + justifyContent: "center", 522 + background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", 523 + color: "rgba(255, 255, 255, 0.5)", 524 + }, 525 + loadingSpinner: { 526 + width: 40, 527 + height: 40, 528 + border: "3px solid var(--atproto-color-border)", 529 + borderTop: "3px solid var(--atproto-color-primary)", 530 + borderRadius: "50%", 531 + animation: "spin 1s linear infinite", 532 + }, 533 + content: { 534 + padding: "24px", 535 + display: "flex", 536 + flexDirection: "column", 537 + gap: "8px", 538 + }, 539 + label: { 540 + fontSize: 11, 541 + fontWeight: 600, 542 + letterSpacing: "0.1em", 543 + textTransform: "uppercase", 544 + color: "var(--atproto-color-text-secondary)", 545 + marginBottom: "4px", 546 + }, 547 + trackName: { 548 + fontSize: 28, 549 + fontWeight: 700, 550 + margin: 0, 551 + lineHeight: 1.2, 552 + color: "var(--atproto-color-text)", 553 + }, 554 + artistName: { 555 + fontSize: 16, 556 + color: "var(--atproto-color-text-secondary)", 557 + marginTop: "4px", 558 + }, 559 + releaseName: { 560 + fontSize: 14, 561 + color: "var(--atproto-color-text-secondary)", 562 + marginTop: "2px", 563 + }, 564 + listenButton: { 565 + display: "inline-flex", 566 + alignItems: "center", 567 + gap: "8px", 568 + marginTop: "16px", 569 + padding: "12px 20px", 570 + background: "var(--atproto-color-bg-elevated)", 571 + border: "1px solid var(--atproto-color-border)", 572 + borderRadius: 24, 573 + color: "var(--atproto-color-text)", 574 + fontSize: 14, 575 + fontWeight: 600, 576 + textDecoration: "none", 577 + cursor: "pointer", 578 + transition: "all 0.2s ease", 579 + alignSelf: "flex-start", 580 + }, 581 + modalOverlay: { 582 + position: "fixed", 583 + top: 0, 584 + left: 0, 585 + right: 0, 586 + bottom: 0, 587 + backgroundColor: "rgba(0, 0, 0, 0.85)", 588 + display: "flex", 589 + alignItems: "center", 590 + justifyContent: "center", 591 + zIndex: 9999, 592 + backdropFilter: "blur(4px)", 593 + }, 594 + modalContent: { 595 + background: "var(--atproto-color-bg)", 596 + borderRadius: 16, 597 + padding: 0, 598 + maxWidth: 450, 599 + width: "90%", 600 + maxHeight: "80vh", 601 + overflow: "auto", 602 + boxShadow: "0 20px 60px rgba(0, 0, 0, 0.8)", 603 + border: "1px solid var(--atproto-color-border)", 604 + }, 605 + modalHeader: { 606 + display: "flex", 607 + justifyContent: "space-between", 608 + alignItems: "center", 609 + padding: "24px 24px 16px 24px", 610 + borderBottom: "1px solid var(--atproto-color-border)", 611 + }, 612 + modalTitle: { 613 + margin: 0, 614 + fontSize: 20, 615 + fontWeight: 700, 616 + color: "var(--atproto-color-text)", 617 + }, 618 + closeButton: { 619 + background: "transparent", 620 + border: "none", 621 + color: "var(--atproto-color-text-secondary)", 622 + fontSize: 32, 623 + cursor: "pointer", 624 + padding: 0, 625 + width: 32, 626 + height: 32, 627 + display: "flex", 628 + alignItems: "center", 629 + justifyContent: "center", 630 + borderRadius: "50%", 631 + transition: "all 0.2s ease", 632 + lineHeight: 1, 633 + }, 634 + platformList: { 635 + padding: "16px", 636 + display: "flex", 637 + flexDirection: "column", 638 + gap: "8px", 639 + }, 640 + platformItem: { 641 + display: "flex", 642 + alignItems: "center", 643 + gap: "16px", 644 + padding: "16px", 645 + background: "var(--atproto-color-bg-hover)", 646 + borderRadius: 12, 647 + textDecoration: "none", 648 + color: "var(--atproto-color-text)", 649 + transition: "all 0.2s ease", 650 + cursor: "pointer", 651 + border: "1px solid var(--atproto-color-border)", 652 + }, 653 + platformIcon: { 654 + fontSize: 24, 655 + width: 32, 656 + height: 32, 657 + display: "flex", 658 + alignItems: "center", 659 + justifyContent: "center", 660 + }, 661 + platformName: { 662 + flex: 1, 663 + fontSize: 16, 664 + fontWeight: 600, 665 + }, 666 + platformArrow: { 667 + opacity: 0.5, 668 + transition: "opacity 0.2s ease", 669 + }, 670 + notListeningContainer: { 671 + fontFamily: "system-ui, -apple-system, sans-serif", 672 + display: "flex", 673 + flexDirection: "column", 674 + alignItems: "center", 675 + justifyContent: "center", 676 + background: "var(--atproto-color-bg)", 677 + borderRadius: 16, 678 + padding: "80px 40px", 679 + maxWidth: 420, 680 + color: "var(--atproto-color-text-secondary)", 681 + border: "1px solid var(--atproto-color-border)", 682 + textAlign: "center", 683 + }, 684 + notListeningIcon: { 685 + width: 120, 686 + height: 120, 687 + borderRadius: "50%", 688 + background: "var(--atproto-color-bg-elevated)", 689 + display: "flex", 690 + alignItems: "center", 691 + justifyContent: "center", 692 + marginBottom: 24, 693 + color: "var(--atproto-color-text-muted)", 694 + }, 695 + notListeningTitle: { 696 + fontSize: 18, 697 + fontWeight: 600, 698 + color: "var(--atproto-color-text)", 699 + marginBottom: 8, 700 + }, 701 + notListeningSubtitle: { 702 + fontSize: 14, 703 + color: "var(--atproto-color-text-secondary)", 704 + }, 705 + }; 706 + 707 + // Add keyframes and hover styles 708 + if (typeof document !== "undefined") { 709 + const styleId = "teal-status-styles"; 710 + if (!document.getElementById(styleId)) { 711 + const styleElement = document.createElement("style"); 712 + styleElement.id = styleId; 713 + styleElement.textContent = ` 714 + @keyframes spin { 715 + 0% { transform: rotate(0deg); } 716 + 100% { transform: rotate(360deg); } 717 + } 718 + 719 + button[data-teal-listen-button]:hover:not(:disabled), 720 + a[data-teal-listen-button]:hover { 721 + background: var(--atproto-color-bg-pressed) !important; 722 + border-color: var(--atproto-color-border-hover) !important; 723 + transform: translateY(-2px); 724 + } 725 + 726 + button[data-teal-listen-button]:disabled { 727 + opacity: 0.5; 728 + cursor: not-allowed; 729 + } 730 + 731 + button[data-teal-close]:hover { 732 + background: var(--atproto-color-bg-hover) !important; 733 + color: var(--atproto-color-text) !important; 734 + } 735 + 736 + a[data-teal-platform]:hover { 737 + background: var(--atproto-color-bg-pressed) !important; 738 + transform: translateX(4px); 739 + } 740 + 741 + a[data-teal-platform]:hover svg { 742 + opacity: 1 !important; 743 + } 744 + `; 745 + document.head.appendChild(styleElement); 746 + } 747 + } 748 + 749 + export default CurrentlyPlayingRenderer;
+971
lib/renderers/GrainGalleryRenderer.tsx
···
··· 1 + import React from "react"; 2 + import type { GrainGalleryRecord, GrainPhotoRecord } from "../types/grain"; 3 + import { useBlob } from "../hooks/useBlob"; 4 + import { isBlobWithCdn, extractCidFromBlob } from "../utils/blob"; 5 + 6 + export interface GrainGalleryPhoto { 7 + record: GrainPhotoRecord; 8 + did: string; 9 + rkey: string; 10 + position?: number; 11 + } 12 + 13 + export interface GrainGalleryRendererProps { 14 + gallery: GrainGalleryRecord; 15 + photos: GrainGalleryPhoto[]; 16 + loading: boolean; 17 + error?: Error; 18 + authorHandle?: string; 19 + authorDisplayName?: string; 20 + avatarUrl?: string; 21 + } 22 + 23 + export const GrainGalleryRenderer: React.FC<GrainGalleryRendererProps> = ({ 24 + gallery, 25 + photos, 26 + loading, 27 + error, 28 + authorDisplayName, 29 + authorHandle, 30 + avatarUrl, 31 + }) => { 32 + const [currentPage, setCurrentPage] = React.useState(0); 33 + const [lightboxOpen, setLightboxOpen] = React.useState(false); 34 + const [lightboxPhotoIndex, setLightboxPhotoIndex] = React.useState(0); 35 + 36 + const createdDate = new Date(gallery.createdAt); 37 + const created = createdDate.toLocaleString(undefined, { 38 + dateStyle: "medium", 39 + timeStyle: "short", 40 + }); 41 + 42 + const primaryName = authorDisplayName || authorHandle || "โ€ฆ"; 43 + 44 + // Memoize sorted photos to prevent re-sorting on every render 45 + const sortedPhotos = React.useMemo( 46 + () => [...photos].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)), 47 + [photos] 48 + ); 49 + 50 + // Open lightbox 51 + const openLightbox = React.useCallback((photoIndex: number) => { 52 + setLightboxPhotoIndex(photoIndex); 53 + setLightboxOpen(true); 54 + }, []); 55 + 56 + // Close lightbox 57 + const closeLightbox = React.useCallback(() => { 58 + setLightboxOpen(false); 59 + }, []); 60 + 61 + // Navigate lightbox 62 + const goToNextPhoto = React.useCallback(() => { 63 + setLightboxPhotoIndex((prev) => (prev + 1) % sortedPhotos.length); 64 + }, [sortedPhotos.length]); 65 + 66 + const goToPrevPhoto = React.useCallback(() => { 67 + setLightboxPhotoIndex((prev) => (prev - 1 + sortedPhotos.length) % sortedPhotos.length); 68 + }, [sortedPhotos.length]); 69 + 70 + // Keyboard navigation 71 + React.useEffect(() => { 72 + if (!lightboxOpen) return; 73 + 74 + const handleKeyDown = (e: KeyboardEvent) => { 75 + if (e.key === "Escape") closeLightbox(); 76 + if (e.key === "ArrowLeft") goToPrevPhoto(); 77 + if (e.key === "ArrowRight") goToNextPhoto(); 78 + }; 79 + 80 + window.addEventListener("keydown", handleKeyDown); 81 + return () => window.removeEventListener("keydown", handleKeyDown); 82 + }, [lightboxOpen, closeLightbox, goToPrevPhoto, goToNextPhoto]); 83 + 84 + const isSinglePhoto = sortedPhotos.length === 1; 85 + 86 + // Preload all photos to avoid loading states when paginating 87 + usePreloadAllPhotos(sortedPhotos); 88 + 89 + // Reset to first page when photos change 90 + React.useEffect(() => { 91 + setCurrentPage(0); 92 + }, [sortedPhotos.length]); 93 + 94 + // Memoize pagination calculations with intelligent photo count per page 95 + const paginationData = React.useMemo(() => { 96 + const pages = calculatePages(sortedPhotos); 97 + const totalPages = pages.length; 98 + const visiblePhotos = pages[currentPage] || []; 99 + const hasMultiplePages = totalPages > 1; 100 + const layoutPhotos = calculateLayout(visiblePhotos); 101 + 102 + return { 103 + pages, 104 + totalPages, 105 + visiblePhotos, 106 + hasMultiplePages, 107 + layoutPhotos, 108 + }; 109 + }, [sortedPhotos, currentPage]); 110 + 111 + const { totalPages, hasMultiplePages, layoutPhotos } = paginationData; 112 + 113 + // Memoize navigation handlers to prevent re-creation 114 + const goToNextPage = React.useCallback(() => { 115 + setCurrentPage((prev) => (prev + 1) % totalPages); 116 + }, [totalPages]); 117 + 118 + const goToPrevPage = React.useCallback(() => { 119 + setCurrentPage((prev) => (prev - 1 + totalPages) % totalPages); 120 + }, [totalPages]); 121 + 122 + if (error) { 123 + return ( 124 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 125 + Failed to load gallery. 126 + </div> 127 + ); 128 + } 129 + 130 + if (loading && photos.length === 0) { 131 + return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loading galleryโ€ฆ</div>; 132 + } 133 + 134 + return ( 135 + <> 136 + {/* Hidden preload elements for all photos */} 137 + <div style={{ display: "none" }} aria-hidden> 138 + {sortedPhotos.map((photo) => ( 139 + <PreloadPhoto key={`${photo.did}-${photo.rkey}-preload`} photo={photo} /> 140 + ))} 141 + </div> 142 + 143 + {/* Lightbox */} 144 + {lightboxOpen && ( 145 + <Lightbox 146 + photo={sortedPhotos[lightboxPhotoIndex]} 147 + photoIndex={lightboxPhotoIndex} 148 + totalPhotos={sortedPhotos.length} 149 + onClose={closeLightbox} 150 + onNext={goToNextPhoto} 151 + onPrev={goToPrevPhoto} 152 + /> 153 + )} 154 + 155 + <article style={styles.card}> 156 + <header style={styles.header}> 157 + {avatarUrl ? ( 158 + <img src={avatarUrl} alt={`${authorDisplayName || authorHandle || 'User'}'s profile picture`} style={styles.avatarImg} /> 159 + ) : ( 160 + <div style={styles.avatarPlaceholder} aria-hidden /> 161 + )} 162 + <div style={styles.authorInfo}> 163 + <strong style={styles.displayName}>{primaryName}</strong> 164 + {authorHandle && ( 165 + <span 166 + style={{ 167 + ...styles.handle, 168 + color: `var(--atproto-color-text-secondary)`, 169 + }} 170 + > 171 + @{authorHandle} 172 + </span> 173 + )} 174 + </div> 175 + </header> 176 + 177 + <div style={styles.galleryInfo}> 178 + <h2 179 + style={{ 180 + ...styles.title, 181 + color: `var(--atproto-color-text)`, 182 + }} 183 + > 184 + {gallery.title} 185 + </h2> 186 + {gallery.description && ( 187 + <p 188 + style={{ 189 + ...styles.description, 190 + color: `var(--atproto-color-text-secondary)`, 191 + }} 192 + > 193 + {gallery.description} 194 + </p> 195 + )} 196 + </div> 197 + 198 + {isSinglePhoto ? ( 199 + <div style={styles.singlePhotoContainer}> 200 + <GalleryPhotoItem 201 + key={`${sortedPhotos[0].did}-${sortedPhotos[0].rkey}`} 202 + photo={sortedPhotos[0]} 203 + isSingle={true} 204 + onClick={() => openLightbox(0)} 205 + /> 206 + </div> 207 + ) : ( 208 + <div style={styles.carouselContainer}> 209 + {hasMultiplePages && currentPage > 0 && ( 210 + <button 211 + onClick={goToPrevPage} 212 + onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")} 213 + onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")} 214 + style={{ 215 + ...styles.navButton, 216 + ...styles.navButtonLeft, 217 + color: "white", 218 + background: "rgba(0, 0, 0, 0.5)", 219 + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", 220 + }} 221 + aria-label="Previous photos" 222 + > 223 + โ€น 224 + </button> 225 + )} 226 + <div style={styles.photosGrid}> 227 + {layoutPhotos.map((item) => { 228 + const photoIndex = sortedPhotos.findIndex(p => p.did === item.did && p.rkey === item.rkey); 229 + return ( 230 + <GalleryPhotoItem 231 + key={`${item.did}-${item.rkey}`} 232 + photo={item} 233 + isSingle={false} 234 + span={item.span} 235 + onClick={() => openLightbox(photoIndex)} 236 + /> 237 + ); 238 + })} 239 + </div> 240 + {hasMultiplePages && currentPage < totalPages - 1 && ( 241 + <button 242 + onClick={goToNextPage} 243 + onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")} 244 + onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")} 245 + style={{ 246 + ...styles.navButton, 247 + ...styles.navButtonRight, 248 + color: "white", 249 + background: "rgba(0, 0, 0, 0.5)", 250 + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", 251 + }} 252 + aria-label="Next photos" 253 + > 254 + โ€บ 255 + </button> 256 + )} 257 + </div> 258 + )} 259 + 260 + <footer style={styles.footer}> 261 + <time 262 + style={{ 263 + ...styles.time, 264 + color: `var(--atproto-color-text-muted)`, 265 + }} 266 + dateTime={gallery.createdAt} 267 + > 268 + {created} 269 + </time> 270 + {hasMultiplePages && !isSinglePhoto && ( 271 + <div style={styles.paginationDots}> 272 + {Array.from({ length: totalPages }, (_, i) => ( 273 + <button 274 + key={i} 275 + onClick={() => setCurrentPage(i)} 276 + style={{ 277 + ...styles.paginationDot, 278 + background: i === currentPage 279 + ? `var(--atproto-color-text)` 280 + : `var(--atproto-color-border)`, 281 + }} 282 + aria-label={`Go to page ${i + 1}`} 283 + aria-current={i === currentPage ? "page" : undefined} 284 + /> 285 + ))} 286 + </div> 287 + )} 288 + </footer> 289 + </article> 290 + </> 291 + ); 292 + }; 293 + 294 + // Component to preload a single photo's blob 295 + const PreloadPhoto: React.FC<{ photo: GrainGalleryPhoto }> = ({ photo }) => { 296 + const photoBlob = photo.record.photo; 297 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 298 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 299 + 300 + // Trigger blob loading via the hook 301 + useBlob(photo.did, cid); 302 + 303 + // Preload CDN images via Image element 304 + React.useEffect(() => { 305 + if (cdnUrl) { 306 + const img = new Image(); 307 + img.src = cdnUrl; 308 + } 309 + }, [cdnUrl]); 310 + 311 + return null; 312 + }; 313 + 314 + // Hook to preload all photos (CDN-based) 315 + const usePreloadAllPhotos = (photos: GrainGalleryPhoto[]) => { 316 + React.useEffect(() => { 317 + // Preload CDN images 318 + photos.forEach((photo) => { 319 + const photoBlob = photo.record.photo; 320 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 321 + 322 + if (cdnUrl) { 323 + const img = new Image(); 324 + img.src = cdnUrl; 325 + } 326 + }); 327 + }, [photos]); 328 + }; 329 + 330 + // Calculate pages with intelligent photo count (1, 2, or 3) 331 + // Only includes multiple photos when they fit well together 332 + const calculatePages = (photos: GrainGalleryPhoto[]): GrainGalleryPhoto[][] => { 333 + if (photos.length === 0) return []; 334 + if (photos.length === 1) return [[photos[0]]]; 335 + 336 + const pages: GrainGalleryPhoto[][] = []; 337 + let i = 0; 338 + 339 + while (i < photos.length) { 340 + const remaining = photos.length - i; 341 + 342 + // Only one photo left - use it 343 + if (remaining === 1) { 344 + pages.push([photos[i]]); 345 + break; 346 + } 347 + 348 + // Check if next 3 photos can fit well together 349 + if (remaining >= 3) { 350 + const nextThree = photos.slice(i, i + 3); 351 + if (canFitThreePhotos(nextThree)) { 352 + pages.push(nextThree); 353 + i += 3; 354 + continue; 355 + } 356 + } 357 + 358 + // Check if next 2 photos can fit well together 359 + if (remaining >= 2) { 360 + const nextTwo = photos.slice(i, i + 2); 361 + if (canFitTwoPhotos(nextTwo)) { 362 + pages.push(nextTwo); 363 + i += 2; 364 + continue; 365 + } 366 + } 367 + 368 + // Photos don't fit well together, use 1 per page 369 + pages.push([photos[i]]); 370 + i += 1; 371 + } 372 + 373 + return pages; 374 + }; 375 + 376 + // Helper functions for aspect ratio classification 377 + const isPortrait = (ratio: number) => ratio < 0.8; 378 + const isLandscape = (ratio: number) => ratio > 1.2; 379 + const isSquarish = (ratio: number) => ratio >= 0.8 && ratio <= 1.2; 380 + 381 + // Determine if 2 photos can fit well together side by side 382 + const canFitTwoPhotos = (photos: GrainGalleryPhoto[]): boolean => { 383 + if (photos.length !== 2) return false; 384 + 385 + const ratios = photos.map((p) => { 386 + const ar = p.record.aspectRatio; 387 + return ar ? ar.width / ar.height : 1; 388 + }); 389 + 390 + const [r1, r2] = ratios; 391 + 392 + // Two portraits side by side don't work well (too narrow) 393 + if (isPortrait(r1) && isPortrait(r2)) return false; 394 + 395 + // Portrait + landscape/square creates awkward layout 396 + if (isPortrait(r1) && !isPortrait(r2)) return false; 397 + if (!isPortrait(r1) && isPortrait(r2)) return false; 398 + 399 + // Two landscape or two squarish photos work well 400 + if ((isLandscape(r1) || isSquarish(r1)) && (isLandscape(r2) || isSquarish(r2))) { 401 + return true; 402 + } 403 + 404 + // Default to not fitting 405 + return false; 406 + }; 407 + 408 + // Determine if 3 photos can fit well together in a layout 409 + const canFitThreePhotos = (photos: GrainGalleryPhoto[]): boolean => { 410 + if (photos.length !== 3) return false; 411 + 412 + const ratios = photos.map((p) => { 413 + const ar = p.record.aspectRatio; 414 + return ar ? ar.width / ar.height : 1; 415 + }); 416 + 417 + const [r1, r2, r3] = ratios; 418 + 419 + // Good pattern: one portrait, two landscape/square 420 + if (isPortrait(r1) && !isPortrait(r2) && !isPortrait(r3)) return true; 421 + if (isPortrait(r3) && !isPortrait(r1) && !isPortrait(r2)) return true; 422 + 423 + // Good pattern: all similar aspect ratios (all landscape or all squarish) 424 + const allLandscape = ratios.every(isLandscape); 425 + const allSquarish = ratios.every(isSquarish); 426 + if (allLandscape || allSquarish) return true; 427 + 428 + // Three portraits in a row can work 429 + const allPortrait = ratios.every(isPortrait); 430 + if (allPortrait) return true; 431 + 432 + // Otherwise don't fit 3 together 433 + return false; 434 + }; 435 + 436 + // Layout calculator for intelligent photo grid arrangement 437 + const calculateLayout = (photos: GrainGalleryPhoto[]) => { 438 + if (photos.length === 0) return []; 439 + if (photos.length === 1) { 440 + return [{ ...photos[0], span: { row: 2, col: 2 } }]; 441 + } 442 + 443 + const photosWithRatios = photos.map((photo) => { 444 + const ratio = photo.record.aspectRatio 445 + ? photo.record.aspectRatio.width / photo.record.aspectRatio.height 446 + : 1; 447 + return { 448 + ...photo, 449 + ratio, 450 + isPortrait: isPortrait(ratio), 451 + isLandscape: isLandscape(ratio) 452 + }; 453 + }); 454 + 455 + // For 2 photos: side by side 456 + if (photos.length === 2) { 457 + return photosWithRatios.map((p) => ({ ...p, span: { row: 2, col: 1 } })); 458 + } 459 + 460 + // For 3 photos: try to create a balanced layout 461 + if (photos.length === 3) { 462 + const [p1, p2, p3] = photosWithRatios; 463 + 464 + // Pattern 1: One tall on left, two stacked on right 465 + if (p1.isPortrait && !p2.isPortrait && !p3.isPortrait) { 466 + return [ 467 + { ...p1, span: { row: 2, col: 1 } }, 468 + { ...p2, span: { row: 1, col: 1 } }, 469 + { ...p3, span: { row: 1, col: 1 } }, 470 + ]; 471 + } 472 + 473 + // Pattern 2: Two stacked on left, one tall on right 474 + if (!p1.isPortrait && !p2.isPortrait && p3.isPortrait) { 475 + return [ 476 + { ...p1, span: { row: 1, col: 1 } }, 477 + { ...p2, span: { row: 1, col: 1 } }, 478 + { ...p3, span: { row: 2, col: 1 } }, 479 + ]; 480 + } 481 + 482 + // Pattern 3: All in a row 483 + const allPortrait = photosWithRatios.every((p) => p.isPortrait); 484 + if (allPortrait) { 485 + // All portraits: display in a row with smaller cells 486 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 487 + } 488 + 489 + // Default: All three in a row 490 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 491 + } 492 + 493 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 494 + }; 495 + 496 + // Lightbox component for fullscreen image viewing 497 + const Lightbox: React.FC<{ 498 + photo: GrainGalleryPhoto; 499 + photoIndex: number; 500 + totalPhotos: number; 501 + onClose: () => void; 502 + onNext: () => void; 503 + onPrev: () => void; 504 + }> = ({ photo, photoIndex, totalPhotos, onClose, onNext, onPrev }) => { 505 + const photoBlob = photo.record.photo; 506 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 507 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 508 + const { url: urlFromBlob, loading: photoLoading, error: photoError } = useBlob(photo.did, cid); 509 + const url = cdnUrl || urlFromBlob; 510 + const alt = photo.record.alt?.trim() || "grain.social photo"; 511 + 512 + return ( 513 + <div 514 + role="dialog" 515 + aria-modal="true" 516 + aria-label={`Photo ${photoIndex + 1} of ${totalPhotos}`} 517 + style={{ 518 + position: "fixed", 519 + top: 0, 520 + left: 0, 521 + right: 0, 522 + bottom: 0, 523 + background: "rgba(0, 0, 0, 0.95)", 524 + zIndex: 9999, 525 + display: "flex", 526 + alignItems: "center", 527 + justifyContent: "center", 528 + padding: 20, 529 + }} 530 + onClick={onClose} 531 + > 532 + {/* Close button */} 533 + <button 534 + onClick={onClose} 535 + style={{ 536 + position: "absolute", 537 + top: 20, 538 + right: 20, 539 + width: 40, 540 + height: 40, 541 + border: "none", 542 + borderRadius: "50%", 543 + background: "rgba(255, 255, 255, 0.1)", 544 + color: "white", 545 + fontSize: 24, 546 + cursor: "pointer", 547 + display: "flex", 548 + alignItems: "center", 549 + justifyContent: "center", 550 + transition: "background 200ms ease", 551 + }} 552 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 553 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 554 + aria-label="Close lightbox" 555 + > 556 + ร— 557 + </button> 558 + 559 + {/* Previous button */} 560 + {totalPhotos > 1 && ( 561 + <button 562 + onClick={(e) => { 563 + e.stopPropagation(); 564 + onPrev(); 565 + }} 566 + style={{ 567 + position: "absolute", 568 + left: 20, 569 + top: "50%", 570 + transform: "translateY(-50%)", 571 + width: 50, 572 + height: 50, 573 + border: "none", 574 + borderRadius: "50%", 575 + background: "rgba(255, 255, 255, 0.1)", 576 + color: "white", 577 + fontSize: 24, 578 + cursor: "pointer", 579 + display: "flex", 580 + alignItems: "center", 581 + justifyContent: "center", 582 + transition: "background 200ms ease", 583 + }} 584 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 585 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 586 + aria-label={`Previous photo (${photoIndex} of ${totalPhotos})`} 587 + > 588 + โ€น 589 + </button> 590 + )} 591 + 592 + {/* Next button */} 593 + {totalPhotos > 1 && ( 594 + <button 595 + onClick={(e) => { 596 + e.stopPropagation(); 597 + onNext(); 598 + }} 599 + style={{ 600 + position: "absolute", 601 + right: 20, 602 + top: "50%", 603 + transform: "translateY(-50%)", 604 + width: 50, 605 + height: 50, 606 + border: "none", 607 + borderRadius: "50%", 608 + background: "rgba(255, 255, 255, 0.1)", 609 + color: "white", 610 + fontSize: 24, 611 + cursor: "pointer", 612 + display: "flex", 613 + alignItems: "center", 614 + justifyContent: "center", 615 + transition: "background 200ms ease", 616 + }} 617 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 618 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 619 + aria-label={`Next photo (${photoIndex + 2} of ${totalPhotos})`} 620 + > 621 + โ€บ 622 + </button> 623 + )} 624 + 625 + {/* Image */} 626 + <div 627 + style={{ 628 + maxWidth: "90vw", 629 + maxHeight: "90vh", 630 + display: "flex", 631 + alignItems: "center", 632 + justifyContent: "center", 633 + }} 634 + onClick={(e) => e.stopPropagation()} 635 + > 636 + {url ? ( 637 + <img 638 + src={url} 639 + alt={alt} 640 + style={{ 641 + maxWidth: "100%", 642 + maxHeight: "100%", 643 + objectFit: "contain", 644 + borderRadius: 8, 645 + }} 646 + /> 647 + ) : ( 648 + <div 649 + style={{ 650 + color: "white", 651 + fontSize: 16, 652 + textAlign: "center", 653 + }} 654 + > 655 + {photoLoading ? "Loadingโ€ฆ" : photoError ? "Failed to load" : "Unavailable"} 656 + </div> 657 + )} 658 + </div> 659 + 660 + {/* Photo counter */} 661 + {totalPhotos > 1 && ( 662 + <div 663 + style={{ 664 + position: "absolute", 665 + bottom: 20, 666 + left: "50%", 667 + transform: "translateX(-50%)", 668 + color: "white", 669 + fontSize: 14, 670 + background: "rgba(0, 0, 0, 0.5)", 671 + padding: "8px 16px", 672 + borderRadius: 20, 673 + }} 674 + > 675 + {photoIndex + 1} / {totalPhotos} 676 + </div> 677 + )} 678 + </div> 679 + ); 680 + }; 681 + 682 + const GalleryPhotoItem: React.FC<{ 683 + photo: GrainGalleryPhoto; 684 + isSingle: boolean; 685 + span?: { row: number; col: number }; 686 + onClick?: () => void; 687 + }> = ({ photo, isSingle, span, onClick }) => { 688 + const [showAltText, setShowAltText] = React.useState(false); 689 + const photoBlob = photo.record.photo; 690 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 691 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 692 + const { url: urlFromBlob, loading: photoLoading, error: photoError } = useBlob(photo.did, cid); 693 + const url = cdnUrl || urlFromBlob; 694 + const alt = photo.record.alt?.trim() || "grain.social photo"; 695 + const hasAlt = photo.record.alt && photo.record.alt.trim().length > 0; 696 + 697 + const aspect = 698 + photo.record.aspectRatio && photo.record.aspectRatio.height > 0 699 + ? `${photo.record.aspectRatio.width} / ${photo.record.aspectRatio.height}` 700 + : undefined; 701 + 702 + const gridItemStyle = span 703 + ? { 704 + gridRow: `span ${span.row}`, 705 + gridColumn: `span ${span.col}`, 706 + } 707 + : {}; 708 + 709 + return ( 710 + <figure style={{ ...(isSingle ? styles.singlePhotoItem : styles.photoItem), ...gridItemStyle }}> 711 + <button 712 + onClick={onClick} 713 + aria-label={hasAlt ? `View photo: ${alt}` : "View photo"} 714 + style={{ 715 + ...(isSingle ? styles.singlePhotoMedia : styles.photoContainer), 716 + background: `var(--atproto-color-image-bg)`, 717 + // Only apply aspect ratio for single photos; grid photos fill their cells 718 + ...(isSingle && aspect ? { aspectRatio: aspect } : {}), 719 + cursor: onClick ? "pointer" : "default", 720 + border: "none", 721 + padding: 0, 722 + display: "block", 723 + width: "100%", 724 + }} 725 + > 726 + {url ? ( 727 + <img src={url} alt={alt} style={isSingle ? styles.photo : styles.photoGrid} /> 728 + ) : ( 729 + <div 730 + style={{ 731 + ...styles.placeholder, 732 + color: `var(--atproto-color-text-muted)`, 733 + }} 734 + > 735 + {photoLoading 736 + ? "Loadingโ€ฆ" 737 + : photoError 738 + ? "Failed to load" 739 + : "Unavailable"} 740 + </div> 741 + )} 742 + {hasAlt && ( 743 + <button 744 + onClick={(e) => { 745 + e.stopPropagation(); 746 + setShowAltText(!showAltText); 747 + }} 748 + style={{ 749 + ...styles.altBadge, 750 + background: showAltText 751 + ? `var(--atproto-color-text)` 752 + : `var(--atproto-color-bg-secondary)`, 753 + color: showAltText 754 + ? `var(--atproto-color-bg)` 755 + : `var(--atproto-color-text)`, 756 + }} 757 + title="Toggle alt text" 758 + aria-label="Toggle alt text" 759 + aria-pressed={showAltText} 760 + > 761 + ALT 762 + </button> 763 + )} 764 + </button> 765 + {hasAlt && showAltText && ( 766 + <figcaption 767 + style={{ 768 + ...styles.caption, 769 + color: `var(--atproto-color-text-secondary)`, 770 + }} 771 + > 772 + {photo.record.alt} 773 + </figcaption> 774 + )} 775 + </figure> 776 + ); 777 + }; 778 + 779 + const styles: Record<string, React.CSSProperties> = { 780 + card: { 781 + borderRadius: 12, 782 + border: `1px solid var(--atproto-color-border)`, 783 + background: `var(--atproto-color-bg)`, 784 + color: `var(--atproto-color-text)`, 785 + fontFamily: "system-ui, sans-serif", 786 + display: "flex", 787 + flexDirection: "column", 788 + maxWidth: 600, 789 + transition: 790 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease", 791 + overflow: "hidden", 792 + }, 793 + header: { 794 + display: "flex", 795 + alignItems: "center", 796 + gap: 12, 797 + padding: 12, 798 + paddingBottom: 0, 799 + }, 800 + avatarPlaceholder: { 801 + width: 32, 802 + height: 32, 803 + borderRadius: "50%", 804 + background: `var(--atproto-color-border)`, 805 + }, 806 + avatarImg: { 807 + width: 32, 808 + height: 32, 809 + borderRadius: "50%", 810 + objectFit: "cover", 811 + }, 812 + authorInfo: { 813 + display: "flex", 814 + flexDirection: "column", 815 + gap: 2, 816 + }, 817 + displayName: { 818 + fontSize: 14, 819 + fontWeight: 600, 820 + }, 821 + handle: { 822 + fontSize: 12, 823 + }, 824 + galleryInfo: { 825 + padding: 12, 826 + paddingBottom: 8, 827 + }, 828 + title: { 829 + margin: 0, 830 + fontSize: 18, 831 + fontWeight: 600, 832 + marginBottom: 4, 833 + }, 834 + description: { 835 + margin: 0, 836 + fontSize: 14, 837 + lineHeight: 1.4, 838 + whiteSpace: "pre-wrap", 839 + }, 840 + singlePhotoContainer: { 841 + padding: 0, 842 + }, 843 + carouselContainer: { 844 + position: "relative", 845 + padding: 4, 846 + }, 847 + photosGrid: { 848 + display: "grid", 849 + gridTemplateColumns: "repeat(2, 1fr)", 850 + gridTemplateRows: "repeat(2, 1fr)", 851 + gap: 4, 852 + minHeight: 400, 853 + }, 854 + navButton: { 855 + position: "absolute", 856 + top: "50%", 857 + transform: "translateY(-50%)", 858 + width: 28, 859 + height: 28, 860 + border: "none", 861 + borderRadius: "50%", 862 + fontSize: 18, 863 + fontWeight: "600", 864 + cursor: "pointer", 865 + display: "flex", 866 + alignItems: "center", 867 + justifyContent: "center", 868 + zIndex: 10, 869 + transition: "opacity 150ms ease", 870 + userSelect: "none", 871 + opacity: 0.7, 872 + }, 873 + navButtonLeft: { 874 + left: 8, 875 + }, 876 + navButtonRight: { 877 + right: 8, 878 + }, 879 + photoItem: { 880 + margin: 0, 881 + display: "flex", 882 + flexDirection: "column", 883 + gap: 4, 884 + }, 885 + singlePhotoItem: { 886 + margin: 0, 887 + display: "flex", 888 + flexDirection: "column", 889 + gap: 8, 890 + }, 891 + photoContainer: { 892 + position: "relative", 893 + width: "100%", 894 + height: "100%", 895 + overflow: "hidden", 896 + borderRadius: 4, 897 + }, 898 + singlePhotoMedia: { 899 + position: "relative", 900 + width: "100%", 901 + overflow: "hidden", 902 + borderRadius: 0, 903 + }, 904 + photo: { 905 + width: "100%", 906 + height: "100%", 907 + objectFit: "cover", 908 + display: "block", 909 + }, 910 + photoGrid: { 911 + width: "100%", 912 + height: "100%", 913 + objectFit: "cover", 914 + display: "block", 915 + }, 916 + placeholder: { 917 + display: "flex", 918 + alignItems: "center", 919 + justifyContent: "center", 920 + width: "100%", 921 + height: "100%", 922 + minHeight: 100, 923 + fontSize: 12, 924 + }, 925 + caption: { 926 + fontSize: 12, 927 + lineHeight: 1.3, 928 + padding: "0 12px 8px", 929 + }, 930 + altBadge: { 931 + position: "absolute", 932 + bottom: 8, 933 + right: 8, 934 + padding: "4px 8px", 935 + fontSize: 10, 936 + fontWeight: 600, 937 + letterSpacing: "0.5px", 938 + border: "none", 939 + borderRadius: 4, 940 + cursor: "pointer", 941 + transition: "background 150ms ease, color 150ms ease", 942 + fontFamily: "system-ui, sans-serif", 943 + }, 944 + footer: { 945 + padding: 12, 946 + paddingTop: 8, 947 + display: "flex", 948 + justifyContent: "space-between", 949 + alignItems: "center", 950 + }, 951 + time: { 952 + fontSize: 11, 953 + }, 954 + paginationDots: { 955 + display: "flex", 956 + gap: 6, 957 + alignItems: "center", 958 + }, 959 + paginationDot: { 960 + width: 6, 961 + height: 6, 962 + borderRadius: "50%", 963 + border: "none", 964 + padding: 0, 965 + cursor: "pointer", 966 + transition: "background 200ms ease, transform 150ms ease", 967 + flexShrink: 0, 968 + }, 969 + }; 970 + 971 + export default GrainGalleryRenderer;
+5 -3
lib/renderers/LeafletDocumentRenderer.tsx
··· 1 import React, { useMemo, useRef } from "react"; 2 import { useDidResolution } from "../hooks/useDidResolution"; 3 import { useBlob } from "../hooks/useBlob"; 4 import { 5 parseAtUri, 6 formatDidForLabel, ··· 54 publicationBaseUrl, 55 publicationRecord, 56 }) => { 57 const authorDid = record.author?.startsWith("did:") 58 ? record.author 59 : undefined; ··· 78 : undefined); 79 const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel; 80 const authorHref = publicationUri 81 - ? `https://bsky.app/profile/${publicationUri.did}` 82 : undefined; 83 84 if (error) ··· 105 timeStyle: "short", 106 }) 107 : undefined; 108 - const fallbackLeafletUrl = `https://bsky.app/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`; 109 const publicationRoot = 110 publicationBaseUrl ?? publicationRecord?.base_path ?? undefined; 111 const resolvedPublicationRoot = publicationRoot ··· 117 publicationLeafletUrl ?? 118 postUrl ?? 119 (publicationUri 120 - ? `https://bsky.app/profile/${publicationUri.did}` 121 : undefined) ?? 122 fallbackLeafletUrl; 123
··· 1 import React, { useMemo, useRef } from "react"; 2 import { useDidResolution } from "../hooks/useDidResolution"; 3 import { useBlob } from "../hooks/useBlob"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 import { 6 parseAtUri, 7 formatDidForLabel, ··· 55 publicationBaseUrl, 56 publicationRecord, 57 }) => { 58 + const { blueskyAppBaseUrl } = useAtProto(); 59 const authorDid = record.author?.startsWith("did:") 60 ? record.author 61 : undefined; ··· 80 : undefined); 81 const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel; 82 const authorHref = publicationUri 83 + ? `${blueskyAppBaseUrl}/profile/${publicationUri.did}` 84 : undefined; 85 86 if (error) ··· 107 timeStyle: "short", 108 }) 109 : undefined; 110 + const fallbackLeafletUrl = `${blueskyAppBaseUrl}/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`; 111 const publicationRoot = 112 publicationBaseUrl ?? publicationRecord?.base_path ?? undefined; 113 const resolvedPublicationRoot = publicationRoot ··· 119 publicationLeafletUrl ?? 120 postUrl ?? 121 (publicationUri 122 + ? `${blueskyAppBaseUrl}/profile/${publicationUri.did}` 123 : undefined) ?? 124 fallbackLeafletUrl; 125
+330
lib/renderers/TangledRepoRenderer.tsx
···
··· 1 + import React from "react"; 2 + import type { TangledRepoRecord } from "../types/tangled"; 3 + import { useAtProto } from "../providers/AtProtoProvider"; 4 + import { useBacklinks } from "../hooks/useBacklinks"; 5 + import { useRepoLanguages } from "../hooks/useRepoLanguages"; 6 + 7 + export interface TangledRepoRendererProps { 8 + record: TangledRepoRecord; 9 + error?: Error; 10 + loading: boolean; 11 + did: string; 12 + rkey: string; 13 + canonicalUrl?: string; 14 + showStarCount?: boolean; 15 + branch?: string; 16 + languages?: string[]; 17 + } 18 + 19 + export const TangledRepoRenderer: React.FC<TangledRepoRendererProps> = ({ 20 + record, 21 + error, 22 + loading, 23 + did, 24 + rkey, 25 + canonicalUrl, 26 + showStarCount = true, 27 + branch, 28 + languages, 29 + }) => { 30 + const { tangledBaseUrl, constellationBaseUrl } = useAtProto(); 31 + 32 + // Construct the AT-URI for this repo record 33 + const atUri = `at://${did}/sh.tangled.repo/${rkey}`; 34 + 35 + // Fetch star backlinks 36 + const { 37 + count: starCount, 38 + loading: starsLoading, 39 + error: starsError, 40 + } = useBacklinks({ 41 + subject: atUri, 42 + source: "sh.tangled.feed.star:subject", 43 + limit: 100, 44 + constellationBaseUrl, 45 + enabled: showStarCount, 46 + }); 47 + 48 + // Extract knot server from record.knot (e.g., "knot.gaze.systems") 49 + const knotUrl = record?.knot 50 + ? record.knot.startsWith("http://") || record.knot.startsWith("https://") 51 + ? new URL(record.knot).hostname 52 + : record.knot 53 + : undefined; 54 + 55 + // Fetch language data from knot server only if languages not provided 56 + const { 57 + data: languagesData, 58 + loading: _languagesLoading, 59 + error: _languagesError, 60 + } = useRepoLanguages({ 61 + knot: knotUrl, 62 + did, 63 + repoName: record?.name, 64 + branch, 65 + enabled: !languages && !!knotUrl && !!record?.name, 66 + }); 67 + 68 + // Convert provided language names to the format expected by the renderer 69 + const providedLanguagesData = languages 70 + ? { 71 + languages: languages.map((name) => ({ 72 + name, 73 + percentage: 0, 74 + size: 0, 75 + })), 76 + ref: branch || "main", 77 + totalFiles: 0, 78 + totalSize: 0, 79 + } 80 + : undefined; 81 + 82 + // Use provided languages or fetched languages 83 + const finalLanguagesData = providedLanguagesData ?? languagesData; 84 + 85 + if (error) 86 + return ( 87 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 88 + Failed to load repository. 89 + </div> 90 + ); 91 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 92 + 93 + // Construct the canonical URL: tangled.org/[did]/[repo-name] 94 + const viewUrl = 95 + canonicalUrl ?? 96 + `${tangledBaseUrl}/${did}/${encodeURIComponent(record.name)}`; 97 + 98 + const tangledIcon = ( 99 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 25 25" style={{ display: "block" }}> 100 + <path fill="currentColor" d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"/> 101 + </svg> 102 + ); 103 + 104 + return ( 105 + <div 106 + style={{ 107 + ...base.container, 108 + background: `var(--atproto-color-bg)`, 109 + borderWidth: "1px", 110 + borderStyle: "solid", 111 + borderColor: `var(--atproto-color-border)`, 112 + color: `var(--atproto-color-text)`, 113 + }} 114 + > 115 + {/* Header with title and icons */} 116 + <div 117 + style={{ 118 + ...base.header, 119 + background: `var(--atproto-color-bg)`, 120 + }} 121 + > 122 + <div style={base.headerTop}> 123 + <strong 124 + style={{ 125 + ...base.repoName, 126 + color: `var(--atproto-color-text)`, 127 + }} 128 + > 129 + {record.name} 130 + </strong> 131 + <div style={base.headerRight}> 132 + <a 133 + href={viewUrl} 134 + target="_blank" 135 + rel="noopener noreferrer" 136 + style={{ 137 + ...base.iconLink, 138 + color: `var(--atproto-color-text)`, 139 + }} 140 + title="View on Tangled" 141 + > 142 + {tangledIcon} 143 + </a> 144 + {record.source && ( 145 + <a 146 + href={record.source} 147 + target="_blank" 148 + rel="noopener noreferrer" 149 + style={{ 150 + ...base.iconLink, 151 + color: `var(--atproto-color-text)`, 152 + }} 153 + title="View source repository" 154 + > 155 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}> 156 + <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/> 157 + </svg> 158 + </a> 159 + )} 160 + </div> 161 + </div> 162 + </div> 163 + 164 + {/* Description */} 165 + {record.description && ( 166 + <div 167 + style={{ 168 + ...base.description, 169 + background: `var(--atproto-color-bg)`, 170 + color: `var(--atproto-color-text-secondary)`, 171 + }} 172 + > 173 + {record.description} 174 + </div> 175 + )} 176 + 177 + {/* Languages and Stars */} 178 + <div 179 + style={{ 180 + ...base.languageSection, 181 + background: `var(--atproto-color-bg)`, 182 + }} 183 + > 184 + {/* Languages */} 185 + {finalLanguagesData && finalLanguagesData.languages.length > 0 && (() => { 186 + const topLanguages = finalLanguagesData.languages 187 + .filter((lang) => lang.name && (lang.percentage > 0 || finalLanguagesData.languages.every(l => l.percentage === 0))) 188 + .sort((a, b) => b.percentage - a.percentage) 189 + .slice(0, 2); 190 + return topLanguages.length > 0 ? ( 191 + <div style={base.languageTags}> 192 + {topLanguages.map((lang) => ( 193 + <span key={lang.name} style={base.languageTag}> 194 + {lang.name} 195 + </span> 196 + ))} 197 + </div> 198 + ) : null; 199 + })()} 200 + 201 + {/* Right side: Stars and View on Tangled link */} 202 + <div style={base.rightSection}> 203 + {/* Stars */} 204 + {showStarCount && ( 205 + <div 206 + style={{ 207 + ...base.starCountContainer, 208 + color: `var(--atproto-color-text-secondary)`, 209 + }} 210 + > 211 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}> 212 + <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/> 213 + </svg> 214 + {starsLoading ? ( 215 + <span style={base.starCount}>...</span> 216 + ) : starsError ? ( 217 + <span style={base.starCount}>โ€”</span> 218 + ) : ( 219 + <span style={base.starCount}>{starCount}</span> 220 + )} 221 + </div> 222 + )} 223 + 224 + {/* View on Tangled link */} 225 + <a 226 + href={viewUrl} 227 + target="_blank" 228 + rel="noopener noreferrer" 229 + style={{ 230 + ...base.viewLink, 231 + color: `var(--atproto-color-link)`, 232 + }} 233 + > 234 + View on Tangled 235 + </a> 236 + </div> 237 + </div> 238 + </div> 239 + ); 240 + }; 241 + 242 + const base: Record<string, React.CSSProperties> = { 243 + container: { 244 + fontFamily: "system-ui, sans-serif", 245 + borderRadius: 6, 246 + overflow: "hidden", 247 + transition: 248 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease", 249 + width: "100%", 250 + }, 251 + header: { 252 + padding: "16px", 253 + display: "flex", 254 + flexDirection: "column", 255 + }, 256 + headerTop: { 257 + display: "flex", 258 + justifyContent: "space-between", 259 + alignItems: "flex-start", 260 + gap: 12, 261 + }, 262 + headerRight: { 263 + display: "flex", 264 + alignItems: "center", 265 + gap: 8, 266 + }, 267 + repoName: { 268 + fontFamily: 269 + 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 270 + fontSize: 18, 271 + fontWeight: 600, 272 + wordBreak: "break-word", 273 + margin: 0, 274 + }, 275 + iconLink: { 276 + display: "flex", 277 + alignItems: "center", 278 + textDecoration: "none", 279 + opacity: 0.7, 280 + transition: "opacity 150ms ease", 281 + }, 282 + description: { 283 + padding: "0 16px 16px 16px", 284 + fontSize: 14, 285 + lineHeight: 1.5, 286 + }, 287 + languageSection: { 288 + padding: "0 16px 16px 16px", 289 + display: "flex", 290 + justifyContent: "space-between", 291 + alignItems: "center", 292 + gap: 12, 293 + flexWrap: "wrap", 294 + }, 295 + languageTags: { 296 + display: "flex", 297 + gap: 8, 298 + flexWrap: "wrap", 299 + }, 300 + languageTag: { 301 + fontSize: 12, 302 + fontWeight: 500, 303 + padding: "4px 10px", 304 + background: `var(--atproto-color-bg)`, 305 + borderRadius: 12, 306 + border: "1px solid var(--atproto-color-border)", 307 + }, 308 + rightSection: { 309 + display: "flex", 310 + alignItems: "center", 311 + gap: 12, 312 + }, 313 + starCountContainer: { 314 + display: "flex", 315 + alignItems: "center", 316 + gap: 4, 317 + fontSize: 13, 318 + }, 319 + starCount: { 320 + fontSize: 13, 321 + fontWeight: 500, 322 + }, 323 + viewLink: { 324 + fontSize: 13, 325 + fontWeight: 500, 326 + textDecoration: "none", 327 + }, 328 + }; 329 + 330 + export default TangledRepoRenderer;
+4 -4
lib/renderers/TangledStringRenderer.tsx
··· 1 import React from "react"; 2 - import type { ShTangledString } from "@atcute/tangled"; 3 - 4 - export type TangledStringRecord = ShTangledString.Main; 5 6 export interface TangledStringRendererProps { 7 record: TangledStringRecord; ··· 20 rkey, 21 canonicalUrl, 22 }) => { 23 24 if (error) 25 return ( ··· 31 32 const viewUrl = 33 canonicalUrl ?? 34 - `https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`; 35 const timestamp = new Date(record.createdAt).toLocaleString(undefined, { 36 dateStyle: "medium", 37 timeStyle: "short",
··· 1 import React from "react"; 2 + import { useAtProto } from "../providers/AtProtoProvider"; 3 + import type { TangledStringRecord } from "../types/tangled"; 4 5 export interface TangledStringRendererProps { 6 record: TangledStringRecord; ··· 19 rkey, 20 canonicalUrl, 21 }) => { 22 + const { tangledBaseUrl } = useAtProto(); 23 24 if (error) 25 return ( ··· 31 32 const viewUrl = 33 canonicalUrl ?? 34 + `${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`; 35 const timestamp = new Date(record.createdAt).toLocaleString(undefined, { 36 dateStyle: "medium", 37 timeStyle: "short",
+59 -47
lib/styles.css
··· 7 8 :root { 9 /* Light theme colors (default) */ 10 - --atproto-color-bg: #ffffff; 11 - --atproto-color-bg-elevated: #f8fafc; 12 - --atproto-color-bg-secondary: #f1f5f9; 13 --atproto-color-text: #0f172a; 14 --atproto-color-text-secondary: #475569; 15 --atproto-color-text-muted: #64748b; 16 - --atproto-color-border: #e2e8f0; 17 - --atproto-color-border-subtle: #cbd5e1; 18 --atproto-color-link: #2563eb; 19 --atproto-color-link-hover: #1d4ed8; 20 --atproto-color-error: #dc2626; 21 - --atproto-color-button-bg: #f1f5f9; 22 - --atproto-color-button-hover: #e2e8f0; 23 --atproto-color-button-text: #0f172a; 24 - --atproto-color-code-bg: #f1f5f9; 25 - --atproto-color-code-border: #e2e8f0; 26 - --atproto-color-blockquote-border: #cbd5e1; 27 - --atproto-color-blockquote-bg: #f8fafc; 28 - --atproto-color-hr: #e2e8f0; 29 - --atproto-color-image-bg: #f1f5f9; 30 --atproto-color-highlight: #fef08a; 31 } 32 33 /* Dark theme - can be applied via [data-theme="dark"] or .dark class */ 34 [data-theme="dark"], 35 .dark { 36 - --atproto-color-bg: #0f172a; 37 - --atproto-color-bg-elevated: #1e293b; 38 - --atproto-color-bg-secondary: #0b1120; 39 - --atproto-color-text: #e2e8f0; 40 - --atproto-color-text-secondary: #94a3b8; 41 - --atproto-color-text-muted: #64748b; 42 - --atproto-color-border: #1e293b; 43 - --atproto-color-border-subtle: #334155; 44 --atproto-color-link: #60a5fa; 45 --atproto-color-link-hover: #93c5fd; 46 --atproto-color-error: #ef4444; 47 - --atproto-color-button-bg: #1e293b; 48 - --atproto-color-button-hover: #334155; 49 - --atproto-color-button-text: #e2e8f0; 50 - --atproto-color-code-bg: #0b1120; 51 - --atproto-color-code-border: #1e293b; 52 - --atproto-color-blockquote-border: #334155; 53 - --atproto-color-blockquote-bg: #1e293b; 54 - --atproto-color-hr: #334155; 55 - --atproto-color-image-bg: #1e293b; 56 --atproto-color-highlight: #854d0e; 57 } 58 ··· 60 @media (prefers-color-scheme: dark) { 61 :root:not([data-theme]), 62 :root[data-theme="system"] { 63 - --atproto-color-bg: #0f172a; 64 - --atproto-color-bg-elevated: #1e293b; 65 - --atproto-color-bg-secondary: #0b1120; 66 - --atproto-color-text: #e2e8f0; 67 - --atproto-color-text-secondary: #94a3b8; 68 - --atproto-color-text-muted: #64748b; 69 - --atproto-color-border: #1e293b; 70 - --atproto-color-border-subtle: #334155; 71 --atproto-color-link: #60a5fa; 72 --atproto-color-link-hover: #93c5fd; 73 --atproto-color-error: #ef4444; 74 - --atproto-color-button-bg: #1e293b; 75 - --atproto-color-button-hover: #334155; 76 - --atproto-color-button-text: #e2e8f0; 77 - --atproto-color-code-bg: #0b1120; 78 - --atproto-color-code-border: #1e293b; 79 - --atproto-color-blockquote-border: #334155; 80 - --atproto-color-blockquote-bg: #1e293b; 81 - --atproto-color-hr: #334155; 82 - --atproto-color-image-bg: #1e293b; 83 --atproto-color-highlight: #854d0e; 84 } 85 }
··· 7 8 :root { 9 /* Light theme colors (default) */ 10 + --atproto-color-bg: #f5f7f9; 11 + --atproto-color-bg-elevated: #f8f9fb; 12 + --atproto-color-bg-secondary: #edf1f5; 13 --atproto-color-text: #0f172a; 14 --atproto-color-text-secondary: #475569; 15 --atproto-color-text-muted: #64748b; 16 + --atproto-color-border: #d6dce3; 17 + --atproto-color-border-subtle: #c1cad4; 18 + --atproto-color-border-hover: #94a3b8; 19 --atproto-color-link: #2563eb; 20 --atproto-color-link-hover: #1d4ed8; 21 --atproto-color-error: #dc2626; 22 + --atproto-color-primary: #2563eb; 23 + --atproto-color-button-bg: #edf1f5; 24 + --atproto-color-button-hover: #e3e9ef; 25 --atproto-color-button-text: #0f172a; 26 + --atproto-color-bg-hover: #f0f3f6; 27 + --atproto-color-bg-pressed: #e3e9ef; 28 + --atproto-color-code-bg: #edf1f5; 29 + --atproto-color-code-border: #d6dce3; 30 + --atproto-color-blockquote-border: #c1cad4; 31 + --atproto-color-blockquote-bg: #f0f3f6; 32 + --atproto-color-hr: #d6dce3; 33 + --atproto-color-image-bg: #edf1f5; 34 --atproto-color-highlight: #fef08a; 35 } 36 37 /* Dark theme - can be applied via [data-theme="dark"] or .dark class */ 38 [data-theme="dark"], 39 .dark { 40 + --atproto-color-bg: #141b22; 41 + --atproto-color-bg-elevated: #1a222a; 42 + --atproto-color-bg-secondary: #0f161c; 43 + --atproto-color-text: #fafafa; 44 + --atproto-color-text-secondary: #a1a1aa; 45 + --atproto-color-text-muted: #71717a; 46 + --atproto-color-border: #1f2933; 47 + --atproto-color-border-subtle: #2d3748; 48 + --atproto-color-border-hover: #4a5568; 49 --atproto-color-link: #60a5fa; 50 --atproto-color-link-hover: #93c5fd; 51 --atproto-color-error: #ef4444; 52 + --atproto-color-primary: #3b82f6; 53 + --atproto-color-button-bg: #1a222a; 54 + --atproto-color-button-hover: #243039; 55 + --atproto-color-button-text: #fafafa; 56 + --atproto-color-bg-hover: #1a222a; 57 + --atproto-color-bg-pressed: #243039; 58 + --atproto-color-code-bg: #0f161c; 59 + --atproto-color-code-border: #1f2933; 60 + --atproto-color-blockquote-border: #2d3748; 61 + --atproto-color-blockquote-bg: #1a222a; 62 + --atproto-color-hr: #243039; 63 + --atproto-color-image-bg: #1a222a; 64 --atproto-color-highlight: #854d0e; 65 } 66 ··· 68 @media (prefers-color-scheme: dark) { 69 :root:not([data-theme]), 70 :root[data-theme="system"] { 71 + --atproto-color-bg: #141b22; 72 + --atproto-color-bg-elevated: #1a222a; 73 + --atproto-color-bg-secondary: #0f161c; 74 + --atproto-color-text: #fafafa; 75 + --atproto-color-text-secondary: #a1a1aa; 76 + --atproto-color-text-muted: #71717a; 77 + --atproto-color-border: #1f2933; 78 + --atproto-color-border-subtle: #2d3748; 79 + --atproto-color-border-hover: #4a5568; 80 --atproto-color-link: #60a5fa; 81 --atproto-color-link-hover: #93c5fd; 82 --atproto-color-error: #ef4444; 83 + --atproto-color-primary: #3b82f6; 84 + --atproto-color-button-bg: #1a222a; 85 + --atproto-color-button-hover: #243039; 86 + --atproto-color-button-text: #fafafa; 87 + --atproto-color-bg-hover: #1a222a; 88 + --atproto-color-bg-pressed: #243039; 89 + --atproto-color-code-bg: #0f161c; 90 + --atproto-color-code-border: #1f2933; 91 + --atproto-color-blockquote-border: #2d3748; 92 + --atproto-color-blockquote-bg: #1a222a; 93 + --atproto-color-hr: #243039; 94 + --atproto-color-image-bg: #1a222a; 95 --atproto-color-highlight: #854d0e; 96 } 97 }
+95
lib/types/grain.ts
···
··· 1 + /** 2 + * Type definitions for grain.social records 3 + * Uses standard atcute blob types for compatibility 4 + */ 5 + import type { Blob } from "@atcute/lexicons/interfaces"; 6 + import type { BlobWithCdn } from "../hooks/useBlueskyAppview"; 7 + 8 + /** 9 + * grain.social gallery record 10 + * A container for a collection of photos 11 + */ 12 + export interface GrainGalleryRecord { 13 + /** 14 + * Record type identifier 15 + */ 16 + $type: "social.grain.gallery"; 17 + /** 18 + * Gallery title 19 + */ 20 + title: string; 21 + /** 22 + * Gallery description 23 + */ 24 + description?: string; 25 + /** 26 + * Self-label values (content warnings) 27 + */ 28 + labels?: { 29 + $type: "com.atproto.label.defs#selfLabels"; 30 + values: Array<{ val: string }>; 31 + }; 32 + /** 33 + * Timestamp when the gallery was created 34 + */ 35 + createdAt: string; 36 + } 37 + 38 + /** 39 + * grain.social gallery item record 40 + * Links a photo to a gallery 41 + */ 42 + export interface GrainGalleryItemRecord { 43 + /** 44 + * Record type identifier 45 + */ 46 + $type: "social.grain.gallery.item"; 47 + /** 48 + * AT URI of the photo (social.grain.photo) 49 + */ 50 + item: string; 51 + /** 52 + * AT URI of the gallery this item belongs to 53 + */ 54 + gallery: string; 55 + /** 56 + * Position/order within the gallery 57 + */ 58 + position?: number; 59 + /** 60 + * Timestamp when the item was added to the gallery 61 + */ 62 + createdAt: string; 63 + } 64 + 65 + /** 66 + * grain.social photo record 67 + * Compatible with records from @atcute clients 68 + */ 69 + export interface GrainPhotoRecord { 70 + /** 71 + * Record type identifier 72 + */ 73 + $type: "social.grain.photo"; 74 + /** 75 + * Alt text description of the image (required for accessibility) 76 + */ 77 + alt: string; 78 + /** 79 + * Photo blob reference - uses standard AT Proto blob format 80 + * Supports any image/* mime type 81 + * May include cdnUrl when fetched from appview 82 + */ 83 + photo: Blob<`image/${string}`> | BlobWithCdn; 84 + /** 85 + * Timestamp when the photo was created 86 + */ 87 + createdAt?: string; 88 + /** 89 + * Aspect ratio of the photo 90 + */ 91 + aspectRatio?: { 92 + width: number; 93 + height: number; 94 + }; 95 + }
+22
lib/types/tangled.ts
···
··· 1 + import type { ShTangledRepo, ShTangledString } from "@atcute/tangled"; 2 + 3 + export type TangledRepoRecord = ShTangledRepo.Main; 4 + export type TangledStringRecord = ShTangledString.Main; 5 + 6 + /** Language information from sh.tangled.repo.languages endpoint */ 7 + export interface RepoLanguage { 8 + name: string; 9 + percentage: number; 10 + size: number; 11 + } 12 + 13 + /** 14 + * Response from sh.tangled.repo.languages endpoint from tangled knot 15 + */ 16 + export interface RepoLanguagesResponse { 17 + languages: RepoLanguage[]; 18 + /** Branch name */ 19 + ref: string; 20 + totalFiles: number; 21 + totalSize: number; 22 + }
+40
lib/types/teal.ts
···
··· 1 + /** 2 + * teal.fm record types for music listening history 3 + * Specification: fm.teal.alpha.actor.status and fm.teal.alpha.feed.play 4 + */ 5 + 6 + export interface TealArtist { 7 + artistName: string; 8 + artistMbId?: string; 9 + } 10 + 11 + export interface TealPlayItem { 12 + artists: TealArtist[]; 13 + originUrl?: string; 14 + trackName: string; 15 + playedTime: string; 16 + releaseName?: string; 17 + recordingMbId?: string; 18 + releaseMbId?: string; 19 + submissionClientAgent?: string; 20 + musicServiceBaseDomain?: string; 21 + isrc?: string; 22 + duration?: number; 23 + } 24 + 25 + /** 26 + * fm.teal.alpha.actor.status - The last played song 27 + */ 28 + export interface TealActorStatusRecord { 29 + $type: "fm.teal.alpha.actor.status"; 30 + item: TealPlayItem; 31 + time: string; 32 + expiry?: string; 33 + } 34 + 35 + /** 36 + * fm.teal.alpha.feed.play - A single play record 37 + */ 38 + export interface TealFeedPlayRecord extends TealPlayItem { 39 + $type: "fm.teal.alpha.feed.play"; 40 + }
+37 -4
lib/utils/atproto-client.ts
··· 13 export interface ServiceResolverOptions { 14 plcDirectory?: string; 15 identityService?: string; 16 fetch?: typeof fetch; 17 } 18 19 const DEFAULT_PLC = "https://plc.directory"; 20 const DEFAULT_IDENTITY_SERVICE = "https://public.api.bsky.app"; 21 const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; 22 const SUPPORTED_DID_METHODS = ["plc", "web"] as const; 23 type SupportedDidMethod = (typeof SUPPORTED_DID_METHODS)[number]; 24 type SupportedDid = Did<SupportedDidMethod>; 25 26 - export const SLINGSHOT_BASE_URL = "https://slingshot.microcosm.blue"; 27 28 export const normalizeBaseUrl = (input: string): string => { 29 const trimmed = input.trim(); ··· 38 39 export class ServiceResolver { 40 private plc: string; 41 private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>; 42 private handleResolver: XrpcHandleResolver; 43 private fetchImpl: typeof fetch; ··· 50 opts.identityService && opts.identityService.trim() 51 ? opts.identityService 52 : DEFAULT_IDENTITY_SERVICE; 53 this.plc = normalizeBaseUrl(plcSource); 54 const identityBase = normalizeBaseUrl(identitySource); 55 this.fetchImpl = bindFetch(opts.fetch); 56 const plcResolver = new PlcDidDocumentResolver({ 57 apiUrl: this.plc, ··· 97 return svc.serviceEndpoint.replace(/\/$/, ""); 98 } 99 100 async resolveHandle(handle: string): Promise<string> { 101 const normalized = handle.trim().toLowerCase(); 102 if (!normalized) throw new Error("Handle cannot be empty"); ··· 104 try { 105 const url = new URL( 106 "/xrpc/com.atproto.identity.resolveHandle", 107 - SLINGSHOT_BASE_URL, 108 ); 109 url.searchParams.set("handle", normalized); 110 const response = await this.fetchImpl(url); ··· 161 } 162 if (!service) throw new Error("service or did required"); 163 const normalizedService = normalizeBaseUrl(service); 164 - const handler = createSlingshotAwareHandler(normalizedService, fetchImpl); 165 const rpc = new Client({ handler }); 166 return { rpc, service: normalizedService, resolver }; 167 } ··· 177 178 function createSlingshotAwareHandler( 179 service: string, 180 fetchImpl: typeof fetch, 181 ): FetchHandler { 182 const primary = simpleFetchHandler({ service, fetch: fetchImpl }); 183 const slingshot = simpleFetchHandler({ 184 - service: SLINGSHOT_BASE_URL, 185 fetch: fetchImpl, 186 }); 187 return async (pathname, init) => {
··· 13 export interface ServiceResolverOptions { 14 plcDirectory?: string; 15 identityService?: string; 16 + slingshotBaseUrl?: string; 17 fetch?: typeof fetch; 18 } 19 20 const DEFAULT_PLC = "https://plc.directory"; 21 const DEFAULT_IDENTITY_SERVICE = "https://public.api.bsky.app"; 22 + const DEFAULT_SLINGSHOT = "https://slingshot.microcosm.blue"; 23 + const DEFAULT_APPVIEW = "https://public.api.bsky.app"; 24 + const DEFAULT_BLUESKY_APP = "https://bsky.app"; 25 + const DEFAULT_TANGLED = "https://tangled.org"; 26 + const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue"; 27 + 28 const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; 29 const SUPPORTED_DID_METHODS = ["plc", "web"] as const; 30 type SupportedDidMethod = (typeof SUPPORTED_DID_METHODS)[number]; 31 type SupportedDid = Did<SupportedDidMethod>; 32 33 + /** 34 + * Default configuration values for AT Protocol services. 35 + * These can be overridden via AtProtoProvider props. 36 + */ 37 + export const DEFAULT_CONFIG = { 38 + plcDirectory: DEFAULT_PLC, 39 + identityService: DEFAULT_IDENTITY_SERVICE, 40 + slingshotBaseUrl: DEFAULT_SLINGSHOT, 41 + blueskyAppviewService: DEFAULT_APPVIEW, 42 + blueskyAppBaseUrl: DEFAULT_BLUESKY_APP, 43 + tangledBaseUrl: DEFAULT_TANGLED, 44 + constellationBaseUrl: DEFAULT_CONSTELLATION, 45 + } as const; 46 + 47 + export const SLINGSHOT_BASE_URL = DEFAULT_SLINGSHOT; 48 49 export const normalizeBaseUrl = (input: string): string => { 50 const trimmed = input.trim(); ··· 59 60 export class ServiceResolver { 61 private plc: string; 62 + private slingshot: string; 63 private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>; 64 private handleResolver: XrpcHandleResolver; 65 private fetchImpl: typeof fetch; ··· 72 opts.identityService && opts.identityService.trim() 73 ? opts.identityService 74 : DEFAULT_IDENTITY_SERVICE; 75 + const slingshotSource = 76 + opts.slingshotBaseUrl && opts.slingshotBaseUrl.trim() 77 + ? opts.slingshotBaseUrl 78 + : DEFAULT_SLINGSHOT; 79 this.plc = normalizeBaseUrl(plcSource); 80 const identityBase = normalizeBaseUrl(identitySource); 81 + this.slingshot = normalizeBaseUrl(slingshotSource); 82 this.fetchImpl = bindFetch(opts.fetch); 83 const plcResolver = new PlcDidDocumentResolver({ 84 apiUrl: this.plc, ··· 124 return svc.serviceEndpoint.replace(/\/$/, ""); 125 } 126 127 + getSlingshotUrl(): string { 128 + return this.slingshot; 129 + } 130 + 131 async resolveHandle(handle: string): Promise<string> { 132 const normalized = handle.trim().toLowerCase(); 133 if (!normalized) throw new Error("Handle cannot be empty"); ··· 135 try { 136 const url = new URL( 137 "/xrpc/com.atproto.identity.resolveHandle", 138 + this.slingshot, 139 ); 140 url.searchParams.set("handle", normalized); 141 const response = await this.fetchImpl(url); ··· 192 } 193 if (!service) throw new Error("service or did required"); 194 const normalizedService = normalizeBaseUrl(service); 195 + const slingshotUrl = resolver.getSlingshotUrl(); 196 + const handler = createSlingshotAwareHandler(normalizedService, slingshotUrl, fetchImpl); 197 const rpc = new Client({ handler }); 198 return { rpc, service: normalizedService, resolver }; 199 } ··· 209 210 function createSlingshotAwareHandler( 211 service: string, 212 + slingshotBaseUrl: string, 213 fetchImpl: typeof fetch, 214 ): FetchHandler { 215 const primary = simpleFetchHandler({ service, fetch: fetchImpl }); 216 const slingshot = simpleFetchHandler({ 217 + service: slingshotBaseUrl, 218 fetch: fetchImpl, 219 }); 220 return async (pathname, init) => {
+120
lib/utils/cache.ts
··· 270 } 271 } 272 }
··· 270 } 271 } 272 } 273 + 274 + interface RecordCacheEntry<T = unknown> { 275 + record: T; 276 + timestamp: number; 277 + } 278 + 279 + interface InFlightRecordEntry<T = unknown> { 280 + promise: Promise<T>; 281 + abort: () => void; 282 + refCount: number; 283 + } 284 + 285 + interface RecordEnsureResult<T = unknown> { 286 + promise: Promise<T>; 287 + release: () => void; 288 + } 289 + 290 + export class RecordCache { 291 + private store = new Map<string, RecordCacheEntry>(); 292 + private inFlight = new Map<string, InFlightRecordEntry>(); 293 + // Collections that should not be cached (e.g., status records that change frequently) 294 + private noCacheCollections = new Set<string>([ 295 + "fm.teal.alpha.actor.status", 296 + "fm.teal.alpha.feed.play", 297 + ]); 298 + 299 + private key(did: string, collection: string, rkey: string): string { 300 + return `${did}::${collection}::${rkey}`; 301 + } 302 + 303 + private shouldCache(collection: string): boolean { 304 + return !this.noCacheCollections.has(collection); 305 + } 306 + 307 + get<T = unknown>( 308 + did?: string, 309 + collection?: string, 310 + rkey?: string, 311 + ): T | undefined { 312 + if (!did || !collection || !rkey) return undefined; 313 + // Don't return cached data for non-cacheable collections 314 + if (!this.shouldCache(collection)) return undefined; 315 + return this.store.get(this.key(did, collection, rkey))?.record as 316 + | T 317 + | undefined; 318 + } 319 + 320 + set<T = unknown>( 321 + did: string, 322 + collection: string, 323 + rkey: string, 324 + record: T, 325 + ): void { 326 + // Don't cache records for non-cacheable collections 327 + if (!this.shouldCache(collection)) return; 328 + this.store.set(this.key(did, collection, rkey), { 329 + record, 330 + timestamp: Date.now(), 331 + }); 332 + } 333 + 334 + ensure<T = unknown>( 335 + did: string, 336 + collection: string, 337 + rkey: string, 338 + loader: () => { promise: Promise<T>; abort: () => void }, 339 + ): RecordEnsureResult<T> { 340 + const cached = this.get<T>(did, collection, rkey); 341 + if (cached !== undefined) { 342 + return { promise: Promise.resolve(cached), release: () => {} }; 343 + } 344 + 345 + const key = this.key(did, collection, rkey); 346 + const existing = this.inFlight.get(key) as 347 + | InFlightRecordEntry<T> 348 + | undefined; 349 + if (existing) { 350 + existing.refCount += 1; 351 + return { 352 + promise: existing.promise, 353 + release: () => this.release(key), 354 + }; 355 + } 356 + 357 + const { promise, abort } = loader(); 358 + const wrapped = promise.then((record) => { 359 + this.set(did, collection, rkey, record); 360 + return record; 361 + }); 362 + 363 + const entry: InFlightRecordEntry<T> = { 364 + promise: wrapped, 365 + abort, 366 + refCount: 1, 367 + }; 368 + 369 + this.inFlight.set(key, entry as InFlightRecordEntry); 370 + 371 + wrapped 372 + .catch(() => {}) 373 + .finally(() => { 374 + this.inFlight.delete(key); 375 + }); 376 + 377 + return { 378 + promise: wrapped, 379 + release: () => this.release(key), 380 + }; 381 + } 382 + 383 + private release(key: string) { 384 + const entry = this.inFlight.get(key); 385 + if (!entry) return; 386 + entry.refCount -= 1; 387 + if (entry.refCount <= 0) { 388 + this.inFlight.delete(key); 389 + entry.abort(); 390 + } 391 + } 392 + }
+1219 -1234
package-lock.json
··· 1 { 2 "name": "atproto-ui", 3 - "version": "0.5.2-beta", 4 "lockfileVersion": 3, 5 "requires": true, 6 "packages": { 7 "": { 8 "name": "atproto-ui", 9 - "version": "0.5.2-beta", 10 "dependencies": { 11 "@atcute/atproto": "^3.1.7", 12 "@atcute/bluesky": "^3.2.3", 13 "@atcute/client": "^4.0.3", 14 "@atcute/identity-resolver": "^1.1.3", 15 - "@atcute/tangled": "^1.0.6" 16 }, 17 "devDependencies": { 18 "@eslint/js": "^9.36.0", ··· 44 } 45 }, 46 "node_modules/@atcute/atproto": { 47 - "version": "3.1.7", 48 - "resolved": "https://registry.npmjs.org/@atcute/atproto/-/atproto-3.1.7.tgz", 49 - "integrity": "sha512-3Ym8qaVZg2vf8qw0KO1aue39z/5oik5J+UDoSes1vr8ddw40UVLA5sV4bXSKmLnhzQHiLLgoVZXe4zaKfozPoQ==", 50 "license": "0BSD", 51 "dependencies": { 52 "@atcute/lexicons": "^1.2.2" 53 } 54 }, 55 "node_modules/@atcute/bluesky": { 56 - "version": "3.2.3", 57 - "resolved": "https://registry.npmjs.org/@atcute/bluesky/-/bluesky-3.2.3.tgz", 58 - "integrity": "sha512-IdPQQ54F1BLhW5z49k81ZUC/GQl/tVygZ+CzLHYvQySHA6GJRcvPzwEf8aV21u0SZOJF+yF4CWEGNgtryyxPmg==", 59 "license": "0BSD", 60 "dependencies": { 61 - "@atcute/atproto": "^3.1.4", 62 - "@atcute/lexicons": "^1.1.1" 63 } 64 }, 65 "node_modules/@atcute/client": { 66 - "version": "4.0.3", 67 - "resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz", 68 - "integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==", 69 - "license": "MIT", 70 "dependencies": { 71 - "@atcute/identity": "^1.0.2", 72 - "@atcute/lexicons": "^1.0.3" 73 } 74 }, 75 "node_modules/@atcute/identity": { 76 - "version": "1.1.0", 77 - "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.0.tgz", 78 - "integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==", 79 "license": "0BSD", 80 "dependencies": { 81 - "@atcute/lexicons": "^1.1.1", 82 - "@badrap/valita": "^0.4.5" 83 } 84 }, 85 "node_modules/@atcute/identity-resolver": { 86 "version": "1.1.4", 87 - "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.4.tgz", 88 - "integrity": "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==", 89 "license": "0BSD", 90 "dependencies": { 91 "@atcute/lexicons": "^1.2.2", ··· 97 } 98 }, 99 "node_modules/@atcute/lexicons": { 100 - "version": "1.2.2", 101 - "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.2.2.tgz", 102 - "integrity": "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==", 103 "license": "0BSD", 104 "dependencies": { 105 "@standard-schema/spec": "^1.0.0", ··· 107 } 108 }, 109 "node_modules/@atcute/tangled": { 110 - "version": "1.0.6", 111 - "resolved": "https://registry.npmjs.org/@atcute/tangled/-/tangled-1.0.6.tgz", 112 - "integrity": "sha512-eEOtrKRbjKfeLYtb5hmkhE45w8h4sV6mT4E2CQzJmhOMGCiK31GX7Vqfh59rhNLb9AlbW72RcQTV737pxx+ksw==", 113 "license": "0BSD", 114 "dependencies": { 115 - "@atcute/atproto": "^3.1.4", 116 - "@atcute/lexicons": "^1.1.1" 117 } 118 }, 119 "node_modules/@atcute/util-fetch": { 120 - "version": "1.0.3", 121 - "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.3.tgz", 122 - "integrity": "sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==", 123 "license": "0BSD", 124 "dependencies": { 125 "@badrap/valita": "^0.4.6" ··· 127 }, 128 "node_modules/@babel/code-frame": { 129 "version": "7.27.1", 130 - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 131 - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 132 "dev": true, 133 "license": "MIT", 134 "dependencies": { ··· 141 } 142 }, 143 "node_modules/@babel/compat-data": { 144 - "version": "7.28.4", 145 - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", 146 - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", 147 "dev": true, 148 "license": "MIT", 149 "engines": { ··· 151 } 152 }, 153 "node_modules/@babel/core": { 154 - "version": "7.28.4", 155 - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", 156 - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", 157 "dev": true, 158 "license": "MIT", 159 "dependencies": { 160 "@babel/code-frame": "^7.27.1", 161 - "@babel/generator": "^7.28.3", 162 "@babel/helper-compilation-targets": "^7.27.2", 163 "@babel/helper-module-transforms": "^7.28.3", 164 "@babel/helpers": "^7.28.4", 165 - "@babel/parser": "^7.28.4", 166 "@babel/template": "^7.27.2", 167 - "@babel/traverse": "^7.28.4", 168 - "@babel/types": "^7.28.4", 169 "@jridgewell/remapping": "^2.3.5", 170 "convert-source-map": "^2.0.0", 171 "debug": "^4.1.0", ··· 181 "url": "https://opencollective.com/babel" 182 } 183 }, 184 "node_modules/@babel/generator": { 185 - "version": "7.28.3", 186 - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", 187 - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", 188 "dev": true, 189 "license": "MIT", 190 "dependencies": { 191 - "@babel/parser": "^7.28.3", 192 - "@babel/types": "^7.28.2", 193 "@jridgewell/gen-mapping": "^0.3.12", 194 "@jridgewell/trace-mapping": "^0.3.28", 195 "jsesc": "^3.0.2" ··· 200 }, 201 "node_modules/@babel/helper-compilation-targets": { 202 "version": "7.27.2", 203 - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 204 - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 205 "dev": true, 206 "license": "MIT", 207 "dependencies": { ··· 215 "node": ">=6.9.0" 216 } 217 }, 218 "node_modules/@babel/helper-globals": { 219 "version": "7.28.0", 220 - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 221 - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 222 "dev": true, 223 "license": "MIT", 224 "engines": { ··· 227 }, 228 "node_modules/@babel/helper-module-imports": { 229 "version": "7.27.1", 230 - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 231 - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 232 "dev": true, 233 "license": "MIT", 234 "dependencies": { ··· 241 }, 242 "node_modules/@babel/helper-module-transforms": { 243 "version": "7.28.3", 244 - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", 245 - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", 246 "dev": true, 247 "license": "MIT", 248 "dependencies": { ··· 259 }, 260 "node_modules/@babel/helper-plugin-utils": { 261 "version": "7.27.1", 262 - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", 263 - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", 264 "dev": true, 265 "license": "MIT", 266 "engines": { ··· 269 }, 270 "node_modules/@babel/helper-string-parser": { 271 "version": "7.27.1", 272 - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 273 - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 274 "dev": true, 275 "license": "MIT", 276 "engines": { ··· 278 } 279 }, 280 "node_modules/@babel/helper-validator-identifier": { 281 - "version": "7.27.1", 282 - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 283 - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 284 "dev": true, 285 "license": "MIT", 286 "engines": { ··· 289 }, 290 "node_modules/@babel/helper-validator-option": { 291 "version": "7.27.1", 292 - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 293 - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 294 "dev": true, 295 "license": "MIT", 296 "engines": { ··· 299 }, 300 "node_modules/@babel/helpers": { 301 "version": "7.28.4", 302 - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", 303 - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", 304 "dev": true, 305 "license": "MIT", 306 "dependencies": { ··· 312 } 313 }, 314 "node_modules/@babel/parser": { 315 - "version": "7.28.4", 316 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", 317 - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", 318 "dev": true, 319 "license": "MIT", 320 "dependencies": { 321 - "@babel/types": "^7.28.4" 322 }, 323 "bin": { 324 "parser": "bin/babel-parser.js" ··· 329 }, 330 "node_modules/@babel/plugin-transform-react-jsx-self": { 331 "version": "7.27.1", 332 - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", 333 - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", 334 "dev": true, 335 "license": "MIT", 336 "dependencies": { ··· 345 }, 346 "node_modules/@babel/plugin-transform-react-jsx-source": { 347 "version": "7.27.1", 348 - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", 349 - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", 350 "dev": true, 351 "license": "MIT", 352 "dependencies": { ··· 361 }, 362 "node_modules/@babel/template": { 363 "version": "7.27.2", 364 - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 365 - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 366 "dev": true, 367 "license": "MIT", 368 "dependencies": { ··· 375 } 376 }, 377 "node_modules/@babel/traverse": { 378 - "version": "7.28.4", 379 - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", 380 - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", 381 "dev": true, 382 "license": "MIT", 383 "dependencies": { 384 "@babel/code-frame": "^7.27.1", 385 - "@babel/generator": "^7.28.3", 386 "@babel/helper-globals": "^7.28.0", 387 - "@babel/parser": "^7.28.4", 388 "@babel/template": "^7.27.2", 389 - "@babel/types": "^7.28.4", 390 "debug": "^4.3.1" 391 }, 392 "engines": { ··· 394 } 395 }, 396 "node_modules/@babel/types": { 397 - "version": "7.28.4", 398 - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", 399 - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", 400 "dev": true, 401 "license": "MIT", 402 "dependencies": { 403 "@babel/helper-string-parser": "^7.27.1", 404 - "@babel/helper-validator-identifier": "^7.27.1" 405 }, 406 "engines": { 407 "node": ">=6.9.0" ··· 409 }, 410 "node_modules/@badrap/valita": { 411 "version": "0.4.6", 412 - "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz", 413 - "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==", 414 "license": "MIT", 415 "engines": { 416 "node": ">= 18" 417 } 418 }, 419 "node_modules/@eslint-community/eslint-utils": { 420 "version": "4.9.0", 421 - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", 422 - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", 423 "dev": true, 424 "license": "MIT", 425 "dependencies": { ··· 437 }, 438 "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 439 "version": "3.4.3", 440 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 441 - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 442 "dev": true, 443 "license": "Apache-2.0", 444 "engines": { ··· 449 } 450 }, 451 "node_modules/@eslint-community/regexpp": { 452 - "version": "4.12.1", 453 - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 454 - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 455 "dev": true, 456 "license": "MIT", 457 "engines": { ··· 459 } 460 }, 461 "node_modules/@eslint/config-array": { 462 - "version": "0.21.0", 463 - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", 464 - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", 465 "dev": true, 466 "license": "Apache-2.0", 467 "dependencies": { 468 - "@eslint/object-schema": "^2.1.6", 469 "debug": "^4.3.1", 470 "minimatch": "^3.1.2" 471 }, ··· 473 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 474 } 475 }, 476 "node_modules/@eslint/config-helpers": { 477 - "version": "0.4.0", 478 - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", 479 - "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", 480 "dev": true, 481 "license": "Apache-2.0", 482 "dependencies": { 483 - "@eslint/core": "^0.16.0" 484 }, 485 "engines": { 486 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 487 } 488 }, 489 "node_modules/@eslint/core": { 490 - "version": "0.16.0", 491 - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", 492 - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", 493 "dev": true, 494 "license": "Apache-2.0", 495 "dependencies": { ··· 500 } 501 }, 502 "node_modules/@eslint/eslintrc": { 503 - "version": "3.3.1", 504 - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", 505 - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 506 "dev": true, 507 "license": "MIT", 508 "dependencies": { ··· 512 "globals": "^14.0.0", 513 "ignore": "^5.2.0", 514 "import-fresh": "^3.2.1", 515 - "js-yaml": "^4.1.0", 516 "minimatch": "^3.1.2", 517 "strip-json-comments": "^3.1.1" 518 }, ··· 523 "url": "https://opencollective.com/eslint" 524 } 525 }, 526 "node_modules/@eslint/eslintrc/node_modules/globals": { 527 "version": "14.0.0", 528 - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 529 - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 530 "dev": true, 531 "license": "MIT", 532 "engines": { ··· 536 "url": "https://github.com/sponsors/sindresorhus" 537 } 538 }, 539 "node_modules/@eslint/js": { 540 - "version": "9.37.0", 541 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", 542 - "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", 543 "dev": true, 544 "license": "MIT", 545 "engines": { ··· 550 } 551 }, 552 "node_modules/@eslint/object-schema": { 553 - "version": "2.1.6", 554 - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 555 - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 556 "dev": true, 557 "license": "Apache-2.0", 558 "engines": { ··· 560 } 561 }, 562 "node_modules/@eslint/plugin-kit": { 563 - "version": "0.4.0", 564 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", 565 - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", 566 "dev": true, 567 "license": "Apache-2.0", 568 "dependencies": { 569 - "@eslint/core": "^0.16.0", 570 "levn": "^0.4.1" 571 }, 572 "engines": { ··· 575 }, 576 "node_modules/@humanfs/core": { 577 "version": "0.19.1", 578 - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 579 - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 580 "dev": true, 581 "license": "Apache-2.0", 582 "engines": { ··· 585 }, 586 "node_modules/@humanfs/node": { 587 "version": "0.16.7", 588 - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 589 - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 590 "dev": true, 591 "license": "Apache-2.0", 592 "dependencies": { ··· 599 }, 600 "node_modules/@humanwhocodes/module-importer": { 601 "version": "1.0.1", 602 - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 603 - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 604 "dev": true, 605 "license": "Apache-2.0", 606 "engines": { ··· 613 }, 614 "node_modules/@humanwhocodes/retry": { 615 "version": "0.4.3", 616 - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 617 - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 618 "dev": true, 619 "license": "Apache-2.0", 620 "engines": { ··· 627 }, 628 "node_modules/@isaacs/balanced-match": { 629 "version": "4.0.1", 630 - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", 631 - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", 632 "dev": true, 633 "license": "MIT", 634 "engines": { ··· 637 }, 638 "node_modules/@isaacs/brace-expansion": { 639 "version": "5.0.0", 640 - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", 641 - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", 642 "dev": true, 643 "license": "MIT", 644 "dependencies": { ··· 650 }, 651 "node_modules/@jridgewell/gen-mapping": { 652 "version": "0.3.13", 653 - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 654 - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 655 "dev": true, 656 "license": "MIT", 657 "dependencies": { ··· 661 }, 662 "node_modules/@jridgewell/remapping": { 663 "version": "2.3.5", 664 - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 665 - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 666 "dev": true, 667 "license": "MIT", 668 "dependencies": { ··· 672 }, 673 "node_modules/@jridgewell/resolve-uri": { 674 "version": "3.1.2", 675 - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 676 - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 677 "dev": true, 678 "license": "MIT", 679 "engines": { ··· 682 }, 683 "node_modules/@jridgewell/source-map": { 684 "version": "0.3.11", 685 - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", 686 - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", 687 "dev": true, 688 "license": "MIT", 689 "optional": true, 690 - "peer": true, 691 "dependencies": { 692 "@jridgewell/gen-mapping": "^0.3.5", 693 "@jridgewell/trace-mapping": "^0.3.25" ··· 695 }, 696 "node_modules/@jridgewell/sourcemap-codec": { 697 "version": "1.5.5", 698 - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 699 - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 700 "dev": true, 701 "license": "MIT" 702 }, 703 "node_modules/@jridgewell/trace-mapping": { 704 "version": "0.3.31", 705 - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 706 - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 707 "dev": true, 708 "license": "MIT", 709 "dependencies": { ··· 712 } 713 }, 714 "node_modules/@microsoft/api-extractor": { 715 - "version": "7.53.1", 716 - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.53.1.tgz", 717 - "integrity": "sha512-bul5eTNxijLdDBqLye74u9494sRmf+9QULtec9Od0uHnifahGeNt8CC4/xCdn7mVyEBrXIQyQ5+sc4Uc0QfBSA==", 718 "dev": true, 719 "license": "MIT", 720 "dependencies": { 721 - "@microsoft/api-extractor-model": "7.31.1", 722 - "@microsoft/tsdoc": "~0.15.1", 723 - "@microsoft/tsdoc-config": "~0.17.1", 724 - "@rushstack/node-core-library": "5.17.0", 725 "@rushstack/rig-package": "0.6.0", 726 - "@rushstack/terminal": "0.19.1", 727 - "@rushstack/ts-command-line": "5.1.1", 728 "lodash": "~4.17.15", 729 "minimatch": "10.0.3", 730 "resolve": "~1.22.1", ··· 737 } 738 }, 739 "node_modules/@microsoft/api-extractor-model": { 740 - "version": "7.31.1", 741 - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.31.1.tgz", 742 - "integrity": "sha512-Dhnip5OFKbl85rq/ICHBFGhV4RA5UQSl8AC/P/zoGvs+CBudPkatt5kIhMGiYgVPnUWmfR6fcp38+1AFLYNtUw==", 743 "dev": true, 744 "license": "MIT", 745 "dependencies": { 746 - "@microsoft/tsdoc": "~0.15.1", 747 - "@microsoft/tsdoc-config": "~0.17.1", 748 - "@rushstack/node-core-library": "5.17.0" 749 - } 750 - }, 751 - "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { 752 - "version": "6.0.0", 753 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 754 - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 755 - "dev": true, 756 - "license": "ISC", 757 - "dependencies": { 758 - "yallist": "^4.0.0" 759 - }, 760 - "engines": { 761 - "node": ">=10" 762 - } 763 - }, 764 - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { 765 - "version": "10.0.3", 766 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", 767 - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", 768 - "dev": true, 769 - "license": "ISC", 770 - "dependencies": { 771 - "@isaacs/brace-expansion": "^5.0.0" 772 - }, 773 - "engines": { 774 - "node": "20 || >=22" 775 - }, 776 - "funding": { 777 - "url": "https://github.com/sponsors/isaacs" 778 - } 779 - }, 780 - "node_modules/@microsoft/api-extractor/node_modules/semver": { 781 - "version": "7.5.4", 782 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 783 - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 784 - "dev": true, 785 - "license": "ISC", 786 - "dependencies": { 787 - "lru-cache": "^6.0.0" 788 - }, 789 - "bin": { 790 - "semver": "bin/semver.js" 791 - }, 792 - "engines": { 793 - "node": ">=10" 794 } 795 }, 796 "node_modules/@microsoft/api-extractor/node_modules/typescript": { 797 "version": "5.8.2", 798 - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 799 - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 800 "dev": true, 801 "license": "Apache-2.0", 802 "bin": { ··· 807 "node": ">=14.17" 808 } 809 }, 810 - "node_modules/@microsoft/api-extractor/node_modules/yallist": { 811 - "version": "4.0.0", 812 - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 813 - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 814 - "dev": true, 815 - "license": "ISC" 816 - }, 817 "node_modules/@microsoft/tsdoc": { 818 - "version": "0.15.1", 819 - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", 820 - "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", 821 "dev": true, 822 "license": "MIT" 823 }, 824 "node_modules/@microsoft/tsdoc-config": { 825 - "version": "0.17.1", 826 - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", 827 - "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", 828 "dev": true, 829 "license": "MIT", 830 "dependencies": { 831 - "@microsoft/tsdoc": "0.15.1", 832 "ajv": "~8.12.0", 833 "jju": "~1.4.0", 834 "resolve": "~1.22.2" ··· 836 }, 837 "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { 838 "version": "8.12.0", 839 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", 840 - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", 841 "dev": true, 842 "license": "MIT", 843 "dependencies": { ··· 851 "url": "https://github.com/sponsors/epoberezkin" 852 } 853 }, 854 - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { 855 - "version": "1.0.0", 856 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 857 - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 858 "dev": true, 859 - "license": "MIT" 860 }, 861 - "node_modules/@nodelib/fs.scandir": { 862 - "version": "2.1.5", 863 - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 864 - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 865 "dev": true, 866 "license": "MIT", 867 - "dependencies": { 868 - "@nodelib/fs.stat": "2.0.5", 869 - "run-parallel": "^1.1.9" 870 - }, 871 "engines": { 872 - "node": ">= 8" 873 } 874 }, 875 - "node_modules/@nodelib/fs.stat": { 876 - "version": "2.0.5", 877 - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 878 - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 879 "dev": true, 880 "license": "MIT", 881 "engines": { 882 - "node": ">= 8" 883 } 884 }, 885 - "node_modules/@nodelib/fs.walk": { 886 - "version": "1.2.8", 887 - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 888 - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 889 "dev": true, 890 "license": "MIT", 891 "dependencies": { 892 - "@nodelib/fs.scandir": "2.1.5", 893 - "fastq": "^1.6.0" 894 }, 895 "engines": { 896 - "node": ">= 8" 897 } 898 }, 899 - "node_modules/@oxc-project/runtime": { 900 - "version": "0.92.0", 901 - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.92.0.tgz", 902 - "integrity": "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==", 903 "dev": true, 904 "license": "MIT", 905 "engines": { 906 "node": "^20.19.0 || >=22.12.0" 907 } 908 }, 909 - "node_modules/@oxc-project/types": { 910 - "version": "0.93.0", 911 - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.93.0.tgz", 912 - "integrity": "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==", 913 "dev": true, 914 "license": "MIT", 915 - "funding": { 916 - "url": "https://github.com/sponsors/Boshen" 917 } 918 }, 919 - "node_modules/@rolldown/binding-darwin-arm64": { 920 "version": "1.0.0-beta.41", 921 - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz", 922 - "integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==", 923 "cpu": [ 924 - "arm64" 925 ], 926 "dev": true, 927 "license": "MIT", 928 "optional": true, 929 "os": [ 930 - "darwin" 931 ], 932 "engines": { 933 "node": "^20.19.0 || >=22.12.0" 934 } 935 }, 936 "node_modules/@rolldown/pluginutils": { 937 - "version": "1.0.0-beta.38", 938 - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", 939 - "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", 940 "dev": true, 941 "license": "MIT" 942 }, 943 "node_modules/@rollup/pluginutils": { 944 - "version": "5.3.0", 945 - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", 946 - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", 947 "dev": true, 948 "license": "MIT", 949 "dependencies": { 950 - "@types/estree": "^1.0.0", 951 - "estree-walker": "^2.0.2", 952 - "picomatch": "^4.0.2" 953 }, 954 "engines": { 955 - "node": ">=14.0.0" 956 - }, 957 - "peerDependencies": { 958 - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 959 - }, 960 - "peerDependenciesMeta": { 961 - "rollup": { 962 - "optional": true 963 - } 964 } 965 }, 966 "node_modules/@rollup/pluginutils/node_modules/picomatch": { 967 - "version": "4.0.3", 968 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 969 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 970 "dev": true, 971 "license": "MIT", 972 "engines": { 973 - "node": ">=12" 974 }, 975 "funding": { 976 "url": "https://github.com/sponsors/jonschlinkert" 977 } 978 }, 979 "node_modules/@rollup/rollup-darwin-arm64": { 980 - "version": "4.52.4", 981 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", 982 - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", 983 "cpu": [ 984 "arm64" 985 ], ··· 988 "optional": true, 989 "os": [ 990 "darwin" 991 ], 992 - "peer": true 993 }, 994 "node_modules/@rushstack/node-core-library": { 995 - "version": "5.17.0", 996 - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.17.0.tgz", 997 - "integrity": "sha512-24vt1GbHN6kyIglRMTVpyEiNRRRJK8uZHc1XoGAhmnTDKnrWet8OmOpImMswJIe6gM78eV8cMg1HXwuUHkSSgg==", 998 "dev": true, 999 "license": "MIT", 1000 "dependencies": { ··· 1018 }, 1019 "node_modules/@rushstack/node-core-library/node_modules/ajv": { 1020 "version": "8.13.0", 1021 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", 1022 - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", 1023 "dev": true, 1024 "license": "MIT", 1025 "dependencies": { ··· 1033 "url": "https://github.com/sponsors/epoberezkin" 1034 } 1035 }, 1036 - "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { 1037 - "version": "1.0.0", 1038 - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", 1039 - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", 1040 "dev": true, 1041 "license": "MIT", 1042 - "peerDependencies": { 1043 - "ajv": "^8.5.0" 1044 - }, 1045 - "peerDependenciesMeta": { 1046 - "ajv": { 1047 - "optional": true 1048 - } 1049 - } 1050 - }, 1051 - "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { 1052 - "version": "1.0.0", 1053 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1054 - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1055 - "dev": true, 1056 - "license": "MIT" 1057 - }, 1058 - "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { 1059 - "version": "6.0.0", 1060 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1061 - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1062 - "dev": true, 1063 - "license": "ISC", 1064 "dependencies": { 1065 - "yallist": "^4.0.0" 1066 - }, 1067 - "engines": { 1068 - "node": ">=10" 1069 - } 1070 - }, 1071 - "node_modules/@rushstack/node-core-library/node_modules/semver": { 1072 - "version": "7.5.4", 1073 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1074 - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1075 - "dev": true, 1076 - "license": "ISC", 1077 - "dependencies": { 1078 - "lru-cache": "^6.0.0" 1079 - }, 1080 - "bin": { 1081 - "semver": "bin/semver.js" 1082 }, 1083 "engines": { 1084 - "node": ">=10" 1085 } 1086 }, 1087 - "node_modules/@rushstack/node-core-library/node_modules/yallist": { 1088 - "version": "4.0.0", 1089 - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1090 - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1091 - "dev": true, 1092 - "license": "ISC" 1093 - }, 1094 "node_modules/@rushstack/problem-matcher": { 1095 "version": "0.1.1", 1096 - "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", 1097 - "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", 1098 "dev": true, 1099 "license": "MIT", 1100 "peerDependencies": { ··· 1108 }, 1109 "node_modules/@rushstack/rig-package": { 1110 "version": "0.6.0", 1111 - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz", 1112 - "integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==", 1113 "dev": true, 1114 "license": "MIT", 1115 "dependencies": { ··· 1118 } 1119 }, 1120 "node_modules/@rushstack/terminal": { 1121 - "version": "0.19.1", 1122 - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.1.tgz", 1123 - "integrity": "sha512-jsBuSad67IDVMO2yp0hDfs0OdE4z3mDIjIL2pclDT3aEJboeZXE85e1HjuD0F6JoW3XgHvDwoX+WOV+AVTDQeA==", 1124 "dev": true, 1125 "license": "MIT", 1126 "dependencies": { 1127 - "@rushstack/node-core-library": "5.17.0", 1128 "@rushstack/problem-matcher": "0.1.1", 1129 "supports-color": "~8.1.1" 1130 }, ··· 1137 } 1138 } 1139 }, 1140 - "node_modules/@rushstack/terminal/node_modules/supports-color": { 1141 - "version": "8.1.1", 1142 - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1143 - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1144 - "dev": true, 1145 - "license": "MIT", 1146 - "dependencies": { 1147 - "has-flag": "^4.0.0" 1148 - }, 1149 - "engines": { 1150 - "node": ">=10" 1151 - }, 1152 - "funding": { 1153 - "url": "https://github.com/chalk/supports-color?sponsor=1" 1154 - } 1155 - }, 1156 "node_modules/@rushstack/ts-command-line": { 1157 - "version": "5.1.1", 1158 - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.1.tgz", 1159 - "integrity": "sha512-HPzFsUcr+wZ3oQI08Ec/E6cuiAVHKzrXZGHhwiwIGygAFiqN5QzX+ff30n70NU2WyE26CykgMwBZZSSyHCJrzA==", 1160 "dev": true, 1161 "license": "MIT", 1162 "dependencies": { 1163 - "@rushstack/terminal": "0.19.1", 1164 "@types/argparse": "1.0.38", 1165 "argparse": "~1.0.9", 1166 "string-argv": "~0.3.1" 1167 } 1168 }, 1169 - "node_modules/@rushstack/ts-command-line/node_modules/argparse": { 1170 - "version": "1.0.10", 1171 - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 1172 - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 1173 "dev": true, 1174 "license": "MIT", 1175 "dependencies": { 1176 - "sprintf-js": "~1.0.2" 1177 } 1178 }, 1179 - "node_modules/@standard-schema/spec": { 1180 - "version": "1.0.0", 1181 - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", 1182 - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", 1183 - "license": "MIT" 1184 - }, 1185 "node_modules/@types/argparse": { 1186 "version": "1.0.38", 1187 - "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", 1188 - "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", 1189 "dev": true, 1190 "license": "MIT" 1191 }, 1192 "node_modules/@types/babel__core": { 1193 "version": "7.20.5", 1194 - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 1195 - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", 1196 "dev": true, 1197 "license": "MIT", 1198 "dependencies": { ··· 1205 }, 1206 "node_modules/@types/babel__generator": { 1207 "version": "7.27.0", 1208 - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", 1209 - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", 1210 "dev": true, 1211 "license": "MIT", 1212 "dependencies": { ··· 1215 }, 1216 "node_modules/@types/babel__template": { 1217 "version": "7.4.4", 1218 - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", 1219 - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", 1220 "dev": true, 1221 "license": "MIT", 1222 "dependencies": { ··· 1226 }, 1227 "node_modules/@types/babel__traverse": { 1228 "version": "7.28.0", 1229 - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", 1230 - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", 1231 "dev": true, 1232 "license": "MIT", 1233 "dependencies": { ··· 1236 }, 1237 "node_modules/@types/estree": { 1238 "version": "1.0.8", 1239 - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 1240 - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 1241 "dev": true, 1242 "license": "MIT" 1243 }, 1244 "node_modules/@types/json-schema": { 1245 "version": "7.0.15", 1246 - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 1247 - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 1248 "dev": true, 1249 "license": "MIT" 1250 }, 1251 "node_modules/@types/node": { 1252 - "version": "24.7.0", 1253 - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", 1254 - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", 1255 "dev": true, 1256 "license": "MIT", 1257 "dependencies": { 1258 - "undici-types": "~7.14.0" 1259 } 1260 }, 1261 "node_modules/@types/react": { 1262 - "version": "19.2.2", 1263 - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", 1264 - "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", 1265 "dev": true, 1266 "license": "MIT", 1267 "dependencies": { 1268 - "csstype": "^3.0.2" 1269 } 1270 }, 1271 "node_modules/@types/react-dom": { 1272 - "version": "19.2.1", 1273 - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz", 1274 - "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", 1275 "dev": true, 1276 "license": "MIT", 1277 "peerDependencies": { ··· 1279 } 1280 }, 1281 "node_modules/@typescript-eslint/eslint-plugin": { 1282 - "version": "8.46.0", 1283 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", 1284 - "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", 1285 "dev": true, 1286 "license": "MIT", 1287 "dependencies": { 1288 "@eslint-community/regexpp": "^4.10.0", 1289 - "@typescript-eslint/scope-manager": "8.46.0", 1290 - "@typescript-eslint/type-utils": "8.46.0", 1291 - "@typescript-eslint/utils": "8.46.0", 1292 - "@typescript-eslint/visitor-keys": "8.46.0", 1293 "graphemer": "^1.4.0", 1294 "ignore": "^7.0.0", 1295 "natural-compare": "^1.4.0", ··· 1303 "url": "https://opencollective.com/typescript-eslint" 1304 }, 1305 "peerDependencies": { 1306 - "@typescript-eslint/parser": "^8.46.0", 1307 "eslint": "^8.57.0 || ^9.0.0", 1308 "typescript": ">=4.8.4 <6.0.0" 1309 } 1310 }, 1311 "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1312 "version": "7.0.5", 1313 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1314 - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1315 "dev": true, 1316 "license": "MIT", 1317 "engines": { ··· 1319 } 1320 }, 1321 "node_modules/@typescript-eslint/parser": { 1322 - "version": "8.46.0", 1323 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz", 1324 - "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", 1325 "dev": true, 1326 "license": "MIT", 1327 "dependencies": { 1328 - "@typescript-eslint/scope-manager": "8.46.0", 1329 - "@typescript-eslint/types": "8.46.0", 1330 - "@typescript-eslint/typescript-estree": "8.46.0", 1331 - "@typescript-eslint/visitor-keys": "8.46.0", 1332 "debug": "^4.3.4" 1333 }, 1334 "engines": { ··· 1344 } 1345 }, 1346 "node_modules/@typescript-eslint/project-service": { 1347 - "version": "8.46.0", 1348 - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz", 1349 - "integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==", 1350 "dev": true, 1351 "license": "MIT", 1352 "dependencies": { 1353 - "@typescript-eslint/tsconfig-utils": "^8.46.0", 1354 - "@typescript-eslint/types": "^8.46.0", 1355 "debug": "^4.3.4" 1356 }, 1357 "engines": { ··· 1366 } 1367 }, 1368 "node_modules/@typescript-eslint/scope-manager": { 1369 - "version": "8.46.0", 1370 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz", 1371 - "integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==", 1372 "dev": true, 1373 "license": "MIT", 1374 "dependencies": { 1375 - "@typescript-eslint/types": "8.46.0", 1376 - "@typescript-eslint/visitor-keys": "8.46.0" 1377 }, 1378 "engines": { 1379 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1384 } 1385 }, 1386 "node_modules/@typescript-eslint/tsconfig-utils": { 1387 - "version": "8.46.0", 1388 - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz", 1389 - "integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==", 1390 "dev": true, 1391 "license": "MIT", 1392 "engines": { ··· 1401 } 1402 }, 1403 "node_modules/@typescript-eslint/type-utils": { 1404 - "version": "8.46.0", 1405 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz", 1406 - "integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==", 1407 "dev": true, 1408 "license": "MIT", 1409 "dependencies": { 1410 - "@typescript-eslint/types": "8.46.0", 1411 - "@typescript-eslint/typescript-estree": "8.46.0", 1412 - "@typescript-eslint/utils": "8.46.0", 1413 "debug": "^4.3.4", 1414 "ts-api-utils": "^2.1.0" 1415 }, ··· 1426 } 1427 }, 1428 "node_modules/@typescript-eslint/types": { 1429 - "version": "8.46.0", 1430 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz", 1431 - "integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==", 1432 "dev": true, 1433 "license": "MIT", 1434 "engines": { ··· 1440 } 1441 }, 1442 "node_modules/@typescript-eslint/typescript-estree": { 1443 - "version": "8.46.0", 1444 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz", 1445 - "integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==", 1446 "dev": true, 1447 "license": "MIT", 1448 "dependencies": { 1449 - "@typescript-eslint/project-service": "8.46.0", 1450 - "@typescript-eslint/tsconfig-utils": "8.46.0", 1451 - "@typescript-eslint/types": "8.46.0", 1452 - "@typescript-eslint/visitor-keys": "8.46.0", 1453 "debug": "^4.3.4", 1454 - "fast-glob": "^3.3.2", 1455 - "is-glob": "^4.0.3", 1456 "minimatch": "^9.0.4", 1457 "semver": "^7.6.0", 1458 "ts-api-utils": "^2.1.0" 1459 }, 1460 "engines": { ··· 1468 "typescript": ">=4.8.4 <6.0.0" 1469 } 1470 }, 1471 - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 1472 - "version": "2.0.2", 1473 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1474 - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1475 - "dev": true, 1476 - "license": "MIT", 1477 - "dependencies": { 1478 - "balanced-match": "^1.0.0" 1479 - } 1480 - }, 1481 "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1482 "version": "9.0.5", 1483 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1484 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1485 "dev": true, 1486 "license": "ISC", 1487 "dependencies": { ··· 1494 "url": "https://github.com/sponsors/isaacs" 1495 } 1496 }, 1497 "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1498 "version": "7.7.3", 1499 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 1500 - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 1501 "dev": true, 1502 "license": "ISC", 1503 "bin": { ··· 1508 } 1509 }, 1510 "node_modules/@typescript-eslint/utils": { 1511 - "version": "8.46.0", 1512 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz", 1513 - "integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==", 1514 "dev": true, 1515 "license": "MIT", 1516 "dependencies": { 1517 "@eslint-community/eslint-utils": "^4.7.0", 1518 - "@typescript-eslint/scope-manager": "8.46.0", 1519 - "@typescript-eslint/types": "8.46.0", 1520 - "@typescript-eslint/typescript-estree": "8.46.0" 1521 }, 1522 "engines": { 1523 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1532 } 1533 }, 1534 "node_modules/@typescript-eslint/visitor-keys": { 1535 - "version": "8.46.0", 1536 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz", 1537 - "integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==", 1538 "dev": true, 1539 "license": "MIT", 1540 "dependencies": { 1541 - "@typescript-eslint/types": "8.46.0", 1542 "eslint-visitor-keys": "^4.2.1" 1543 }, 1544 "engines": { ··· 1550 } 1551 }, 1552 "node_modules/@vitejs/plugin-react": { 1553 - "version": "5.0.4", 1554 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", 1555 - "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", 1556 "dev": true, 1557 "license": "MIT", 1558 "dependencies": { 1559 - "@babel/core": "^7.28.4", 1560 "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1561 "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1562 - "@rolldown/pluginutils": "1.0.0-beta.38", 1563 "@types/babel__core": "^7.20.5", 1564 - "react-refresh": "^0.17.0" 1565 }, 1566 "engines": { 1567 "node": "^20.19.0 || >=22.12.0" ··· 1571 } 1572 }, 1573 "node_modules/@volar/language-core": { 1574 - "version": "2.4.23", 1575 - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", 1576 - "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", 1577 "dev": true, 1578 "license": "MIT", 1579 "dependencies": { 1580 - "@volar/source-map": "2.4.23" 1581 } 1582 }, 1583 "node_modules/@volar/source-map": { 1584 - "version": "2.4.23", 1585 - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", 1586 - "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", 1587 "dev": true, 1588 "license": "MIT" 1589 }, 1590 "node_modules/@volar/typescript": { 1591 - "version": "2.4.23", 1592 - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", 1593 - "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", 1594 "dev": true, 1595 "license": "MIT", 1596 "dependencies": { 1597 - "@volar/language-core": "2.4.23", 1598 "path-browserify": "^1.0.1", 1599 "vscode-uri": "^3.0.8" 1600 } 1601 }, 1602 "node_modules/acorn": { 1603 "version": "8.15.0", 1604 - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 1605 - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 1606 "dev": true, 1607 "license": "MIT", 1608 "bin": { 1609 "acorn": "bin/acorn" 1610 }, ··· 1614 }, 1615 "node_modules/acorn-jsx": { 1616 "version": "5.3.2", 1617 - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1618 - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1619 "dev": true, 1620 "license": "MIT", 1621 "peerDependencies": { ··· 1623 } 1624 }, 1625 "node_modules/ajv": { 1626 - "version": "6.12.6", 1627 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 1628 - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 1629 "dev": true, 1630 "license": "MIT", 1631 "dependencies": { 1632 - "fast-deep-equal": "^3.1.1", 1633 - "fast-json-stable-stringify": "^2.0.0", 1634 - "json-schema-traverse": "^0.4.1", 1635 - "uri-js": "^4.2.2" 1636 }, 1637 "funding": { 1638 "type": "github", 1639 "url": "https://github.com/sponsors/epoberezkin" 1640 } 1641 }, 1642 "node_modules/ajv-formats": { 1643 "version": "3.0.1", 1644 - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", 1645 - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", 1646 "dev": true, 1647 "license": "MIT", 1648 "dependencies": { ··· 1657 } 1658 } 1659 }, 1660 - "node_modules/ajv-formats/node_modules/ajv": { 1661 - "version": "8.17.1", 1662 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 1663 - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 1664 - "dev": true, 1665 - "license": "MIT", 1666 - "dependencies": { 1667 - "fast-deep-equal": "^3.1.3", 1668 - "fast-uri": "^3.0.1", 1669 - "json-schema-traverse": "^1.0.0", 1670 - "require-from-string": "^2.0.2" 1671 - }, 1672 - "funding": { 1673 - "type": "github", 1674 - "url": "https://github.com/sponsors/epoberezkin" 1675 - } 1676 - }, 1677 - "node_modules/ajv-formats/node_modules/json-schema-traverse": { 1678 - "version": "1.0.0", 1679 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1680 - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1681 - "dev": true, 1682 - "license": "MIT" 1683 - }, 1684 "node_modules/ansi-styles": { 1685 "version": "4.3.0", 1686 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1687 - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1688 "dev": true, 1689 "license": "MIT", 1690 "dependencies": { ··· 1699 }, 1700 "node_modules/ansis": { 1701 "version": "4.2.0", 1702 - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", 1703 - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", 1704 "dev": true, 1705 "license": "ISC", 1706 "engines": { ··· 1708 } 1709 }, 1710 "node_modules/argparse": { 1711 - "version": "2.0.1", 1712 - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1713 - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1714 "dev": true, 1715 - "license": "Python-2.0" 1716 }, 1717 "node_modules/balanced-match": { 1718 "version": "1.0.2", 1719 - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1720 - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1721 "dev": true, 1722 "license": "MIT" 1723 }, 1724 "node_modules/baseline-browser-mapping": { 1725 - "version": "2.8.13", 1726 - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", 1727 - "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", 1728 "dev": true, 1729 "license": "Apache-2.0", 1730 "bin": { ··· 1733 }, 1734 "node_modules/brace-expansion": { 1735 "version": "1.1.12", 1736 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 1737 - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 1738 "dev": true, 1739 "license": "MIT", 1740 "dependencies": { ··· 1742 "concat-map": "0.0.1" 1743 } 1744 }, 1745 - "node_modules/braces": { 1746 - "version": "3.0.3", 1747 - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 1748 - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 1749 - "dev": true, 1750 - "license": "MIT", 1751 - "dependencies": { 1752 - "fill-range": "^7.1.1" 1753 - }, 1754 - "engines": { 1755 - "node": ">=8" 1756 - } 1757 - }, 1758 "node_modules/browserslist": { 1759 - "version": "4.26.3", 1760 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", 1761 - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", 1762 "dev": true, 1763 "funding": [ 1764 { ··· 1775 } 1776 ], 1777 "license": "MIT", 1778 "dependencies": { 1779 - "baseline-browser-mapping": "^2.8.9", 1780 - "caniuse-lite": "^1.0.30001746", 1781 - "electron-to-chromium": "^1.5.227", 1782 - "node-releases": "^2.0.21", 1783 - "update-browserslist-db": "^1.1.3" 1784 }, 1785 "bin": { 1786 "browserslist": "cli.js" ··· 1791 }, 1792 "node_modules/buffer-from": { 1793 "version": "1.1.2", 1794 - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 1795 - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 1796 "dev": true, 1797 "license": "MIT", 1798 - "optional": true, 1799 - "peer": true 1800 }, 1801 "node_modules/callsites": { 1802 "version": "3.1.0", 1803 - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1804 - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1805 "dev": true, 1806 "license": "MIT", 1807 "engines": { ··· 1809 } 1810 }, 1811 "node_modules/caniuse-lite": { 1812 - "version": "1.0.30001748", 1813 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", 1814 - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", 1815 "dev": true, 1816 "funding": [ 1817 { ··· 1831 }, 1832 "node_modules/chalk": { 1833 "version": "4.1.2", 1834 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1835 - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1836 "dev": true, 1837 "license": "MIT", 1838 "dependencies": { ··· 1846 "url": "https://github.com/chalk/chalk?sponsor=1" 1847 } 1848 }, 1849 "node_modules/color-convert": { 1850 "version": "2.0.1", 1851 - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1852 - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1853 "dev": true, 1854 "license": "MIT", 1855 "dependencies": { ··· 1861 }, 1862 "node_modules/color-name": { 1863 "version": "1.1.4", 1864 - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1865 - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1866 "dev": true, 1867 "license": "MIT" 1868 }, 1869 "node_modules/commander": { 1870 "version": "2.20.3", 1871 - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1872 - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 1873 "dev": true, 1874 "license": "MIT", 1875 - "optional": true, 1876 - "peer": true 1877 }, 1878 "node_modules/commondir": { 1879 "version": "1.0.1", 1880 - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 1881 - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", 1882 "dev": true, 1883 "license": "MIT" 1884 }, 1885 "node_modules/compare-versions": { 1886 "version": "6.1.1", 1887 - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", 1888 - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", 1889 "dev": true, 1890 "license": "MIT" 1891 }, 1892 "node_modules/concat-map": { 1893 "version": "0.0.1", 1894 - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1895 - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1896 "dev": true, 1897 "license": "MIT" 1898 }, 1899 "node_modules/confbox": { 1900 "version": "0.2.2", 1901 - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", 1902 - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", 1903 "dev": true, 1904 "license": "MIT" 1905 }, 1906 "node_modules/convert-source-map": { 1907 "version": "2.0.0", 1908 - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1909 - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1910 "dev": true, 1911 "license": "MIT" 1912 }, 1913 "node_modules/cross-spawn": { 1914 "version": "7.0.6", 1915 - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1916 - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1917 "dev": true, 1918 "license": "MIT", 1919 "dependencies": { ··· 1926 } 1927 }, 1928 "node_modules/csstype": { 1929 - "version": "3.1.3", 1930 - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1931 - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 1932 "dev": true, 1933 "license": "MIT" 1934 }, 1935 "node_modules/debug": { 1936 "version": "4.4.3", 1937 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1938 - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1939 "dev": true, 1940 "license": "MIT", 1941 "dependencies": { ··· 1952 }, 1953 "node_modules/deep-is": { 1954 "version": "0.1.4", 1955 - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 1956 - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 1957 "dev": true, 1958 "license": "MIT" 1959 }, 1960 "node_modules/detect-libc": { 1961 "version": "2.1.2", 1962 - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1963 - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1964 "dev": true, 1965 "license": "Apache-2.0", 1966 "engines": { 1967 "node": ">=8" 1968 } 1969 }, 1970 "node_modules/electron-to-chromium": { 1971 - "version": "1.5.232", 1972 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz", 1973 - "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==", 1974 "dev": true, 1975 "license": "ISC" 1976 }, 1977 "node_modules/escalade": { 1978 "version": "3.2.0", 1979 - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 1980 - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 1981 "dev": true, 1982 "license": "MIT", 1983 "engines": { ··· 1986 }, 1987 "node_modules/escape-string-regexp": { 1988 "version": "4.0.0", 1989 - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1990 - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1991 "dev": true, 1992 "license": "MIT", 1993 "engines": { ··· 1998 } 1999 }, 2000 "node_modules/eslint": { 2001 - "version": "9.37.0", 2002 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", 2003 - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", 2004 "dev": true, 2005 "license": "MIT", 2006 "dependencies": { 2007 "@eslint-community/eslint-utils": "^4.8.0", 2008 "@eslint-community/regexpp": "^4.12.1", 2009 - "@eslint/config-array": "^0.21.0", 2010 - "@eslint/config-helpers": "^0.4.0", 2011 - "@eslint/core": "^0.16.0", 2012 "@eslint/eslintrc": "^3.3.1", 2013 - "@eslint/js": "9.37.0", 2014 - "@eslint/plugin-kit": "^0.4.0", 2015 "@humanfs/node": "^0.16.6", 2016 "@humanwhocodes/module-importer": "^1.0.1", 2017 "@humanwhocodes/retry": "^0.4.2", 2018 "@types/estree": "^1.0.6", 2019 - "@types/json-schema": "^7.0.15", 2020 "ajv": "^6.12.4", 2021 "chalk": "^4.0.0", 2022 "cross-spawn": "^7.0.6", ··· 2060 }, 2061 "node_modules/eslint-plugin-react-hooks": { 2062 "version": "5.2.0", 2063 - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", 2064 - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", 2065 "dev": true, 2066 "license": "MIT", 2067 "engines": { ··· 2072 } 2073 }, 2074 "node_modules/eslint-plugin-react-refresh": { 2075 - "version": "0.4.23", 2076 - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz", 2077 - "integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==", 2078 "dev": true, 2079 "license": "MIT", 2080 "peerDependencies": { ··· 2083 }, 2084 "node_modules/eslint-scope": { 2085 "version": "8.4.0", 2086 - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 2087 - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 2088 "dev": true, 2089 "license": "BSD-2-Clause", 2090 "dependencies": { ··· 2100 }, 2101 "node_modules/eslint-visitor-keys": { 2102 "version": "4.2.1", 2103 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 2104 - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 2105 "dev": true, 2106 "license": "Apache-2.0", 2107 "engines": { ··· 2111 "url": "https://opencollective.com/eslint" 2112 } 2113 }, 2114 "node_modules/esm-env": { 2115 "version": "1.2.2", 2116 - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 2117 - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 2118 "license": "MIT" 2119 }, 2120 "node_modules/espree": { 2121 "version": "10.4.0", 2122 - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 2123 - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 2124 "dev": true, 2125 "license": "BSD-2-Clause", 2126 "dependencies": { ··· 2137 }, 2138 "node_modules/esquery": { 2139 "version": "1.6.0", 2140 - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 2141 - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 2142 "dev": true, 2143 "license": "BSD-3-Clause", 2144 "dependencies": { ··· 2150 }, 2151 "node_modules/esrecurse": { 2152 "version": "4.3.0", 2153 - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 2154 - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 2155 "dev": true, 2156 "license": "BSD-2-Clause", 2157 "dependencies": { ··· 2163 }, 2164 "node_modules/estraverse": { 2165 "version": "5.3.0", 2166 - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 2167 - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 2168 "dev": true, 2169 "license": "BSD-2-Clause", 2170 "engines": { ··· 2173 }, 2174 "node_modules/estree-walker": { 2175 "version": "2.0.2", 2176 - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 2177 - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 2178 "dev": true, 2179 "license": "MIT" 2180 }, 2181 "node_modules/esutils": { 2182 "version": "2.0.3", 2183 - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 2184 - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 2185 "dev": true, 2186 "license": "BSD-2-Clause", 2187 "engines": { ··· 2189 } 2190 }, 2191 "node_modules/exsolve": { 2192 - "version": "1.0.7", 2193 - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", 2194 - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", 2195 "dev": true, 2196 "license": "MIT" 2197 }, 2198 "node_modules/fast-deep-equal": { 2199 "version": "3.1.3", 2200 - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 2201 - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 2202 "dev": true, 2203 "license": "MIT" 2204 }, 2205 - "node_modules/fast-glob": { 2206 - "version": "3.3.3", 2207 - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 2208 - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 2209 - "dev": true, 2210 - "license": "MIT", 2211 - "dependencies": { 2212 - "@nodelib/fs.stat": "^2.0.2", 2213 - "@nodelib/fs.walk": "^1.2.3", 2214 - "glob-parent": "^5.1.2", 2215 - "merge2": "^1.3.0", 2216 - "micromatch": "^4.0.8" 2217 - }, 2218 - "engines": { 2219 - "node": ">=8.6.0" 2220 - } 2221 - }, 2222 - "node_modules/fast-glob/node_modules/glob-parent": { 2223 - "version": "5.1.2", 2224 - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 2225 - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 2226 - "dev": true, 2227 - "license": "ISC", 2228 - "dependencies": { 2229 - "is-glob": "^4.0.1" 2230 - }, 2231 - "engines": { 2232 - "node": ">= 6" 2233 - } 2234 - }, 2235 "node_modules/fast-json-stable-stringify": { 2236 "version": "2.1.0", 2237 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", ··· 2241 }, 2242 "node_modules/fast-levenshtein": { 2243 "version": "2.0.6", 2244 - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 2245 - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 2246 "dev": true, 2247 "license": "MIT" 2248 }, 2249 "node_modules/fast-uri": { 2250 "version": "3.1.0", 2251 - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", 2252 - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", 2253 "dev": true, 2254 "funding": [ 2255 { ··· 2263 ], 2264 "license": "BSD-3-Clause" 2265 }, 2266 - "node_modules/fastq": { 2267 - "version": "1.19.1", 2268 - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 2269 - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 2270 "dev": true, 2271 - "license": "ISC", 2272 - "dependencies": { 2273 - "reusify": "^1.0.4" 2274 } 2275 }, 2276 "node_modules/file-entry-cache": { 2277 "version": "8.0.0", 2278 - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 2279 - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 2280 "dev": true, 2281 "license": "MIT", 2282 "dependencies": { ··· 2286 "node": ">=16.0.0" 2287 } 2288 }, 2289 - "node_modules/fill-range": { 2290 - "version": "7.1.1", 2291 - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 2292 - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 2293 - "dev": true, 2294 - "license": "MIT", 2295 - "dependencies": { 2296 - "to-regex-range": "^5.0.1" 2297 - }, 2298 - "engines": { 2299 - "node": ">=8" 2300 - } 2301 - }, 2302 "node_modules/find-cache-dir": { 2303 "version": "3.3.2", 2304 - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", 2305 - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", 2306 "dev": true, 2307 "license": "MIT", 2308 "dependencies": { ··· 2319 }, 2320 "node_modules/find-up": { 2321 "version": "5.0.0", 2322 - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 2323 - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 2324 "dev": true, 2325 "license": "MIT", 2326 "dependencies": { ··· 2336 }, 2337 "node_modules/flat-cache": { 2338 "version": "4.0.1", 2339 - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 2340 - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 2341 "dev": true, 2342 "license": "MIT", 2343 "dependencies": { ··· 2350 }, 2351 "node_modules/flatted": { 2352 "version": "3.3.3", 2353 - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 2354 - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 2355 "dev": true, 2356 "license": "ISC" 2357 }, 2358 "node_modules/fs-extra": { 2359 - "version": "11.3.2", 2360 - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", 2361 - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", 2362 "dev": true, 2363 "license": "MIT", 2364 "dependencies": { ··· 2367 "universalify": "^2.0.0" 2368 }, 2369 "engines": { 2370 - "node": ">=14.14" 2371 } 2372 }, 2373 "node_modules/fsevents": { ··· 2387 }, 2388 "node_modules/function-bind": { 2389 "version": "1.1.2", 2390 - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 2391 - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 2392 "dev": true, 2393 "license": "MIT", 2394 "funding": { ··· 2397 }, 2398 "node_modules/gensync": { 2399 "version": "1.0.0-beta.2", 2400 - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 2401 - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 2402 "dev": true, 2403 "license": "MIT", 2404 "engines": { ··· 2407 }, 2408 "node_modules/glob-parent": { 2409 "version": "6.0.2", 2410 - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 2411 - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 2412 "dev": true, 2413 "license": "ISC", 2414 "dependencies": { ··· 2419 } 2420 }, 2421 "node_modules/globals": { 2422 - "version": "16.4.0", 2423 - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", 2424 - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", 2425 "dev": true, 2426 "license": "MIT", 2427 "engines": { ··· 2433 }, 2434 "node_modules/graceful-fs": { 2435 "version": "4.2.11", 2436 - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 2437 - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 2438 "dev": true, 2439 "license": "ISC" 2440 }, 2441 "node_modules/graphemer": { 2442 "version": "1.4.0", 2443 - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 2444 - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 2445 "dev": true, 2446 "license": "MIT" 2447 }, 2448 "node_modules/has-flag": { 2449 "version": "4.0.0", 2450 - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 2451 - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 2452 "dev": true, 2453 "license": "MIT", 2454 "engines": { ··· 2457 }, 2458 "node_modules/hasown": { 2459 "version": "2.0.2", 2460 - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 2461 - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 2462 "dev": true, 2463 "license": "MIT", 2464 "dependencies": { ··· 2470 }, 2471 "node_modules/ignore": { 2472 "version": "5.3.2", 2473 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 2474 - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 2475 "dev": true, 2476 "license": "MIT", 2477 "engines": { ··· 2480 }, 2481 "node_modules/import-fresh": { 2482 "version": "3.3.1", 2483 - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 2484 - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 2485 "dev": true, 2486 "license": "MIT", 2487 "dependencies": { ··· 2497 }, 2498 "node_modules/import-lazy": { 2499 "version": "4.0.0", 2500 - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", 2501 - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", 2502 "dev": true, 2503 "license": "MIT", 2504 "engines": { ··· 2507 }, 2508 "node_modules/imurmurhash": { 2509 "version": "0.1.4", 2510 - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 2511 - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 2512 "dev": true, 2513 "license": "MIT", 2514 "engines": { ··· 2517 }, 2518 "node_modules/is-core-module": { 2519 "version": "2.16.1", 2520 - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", 2521 - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", 2522 "dev": true, 2523 "license": "MIT", 2524 "dependencies": { ··· 2533 }, 2534 "node_modules/is-extglob": { 2535 "version": "2.1.1", 2536 - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 2537 - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 2538 "dev": true, 2539 "license": "MIT", 2540 "engines": { ··· 2543 }, 2544 "node_modules/is-glob": { 2545 "version": "4.0.3", 2546 - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 2547 - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 2548 "dev": true, 2549 "license": "MIT", 2550 "dependencies": { ··· 2554 "node": ">=0.10.0" 2555 } 2556 }, 2557 - "node_modules/is-number": { 2558 - "version": "7.0.0", 2559 - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 2560 - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 2561 - "dev": true, 2562 - "license": "MIT", 2563 - "engines": { 2564 - "node": ">=0.12.0" 2565 - } 2566 - }, 2567 "node_modules/isexe": { 2568 "version": "2.0.0", 2569 - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2570 - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2571 "dev": true, 2572 "license": "ISC" 2573 }, 2574 "node_modules/jju": { 2575 "version": "1.4.0", 2576 - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", 2577 - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", 2578 "dev": true, 2579 "license": "MIT" 2580 }, 2581 "node_modules/js-tokens": { 2582 "version": "4.0.0", 2583 - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2584 - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2585 "dev": true, 2586 "license": "MIT" 2587 }, 2588 "node_modules/js-yaml": { 2589 - "version": "4.1.0", 2590 - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 2591 - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 2592 "dev": true, 2593 "license": "MIT", 2594 "dependencies": { ··· 2598 "js-yaml": "bin/js-yaml.js" 2599 } 2600 }, 2601 "node_modules/jsesc": { 2602 "version": "3.1.0", 2603 - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 2604 - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 2605 "dev": true, 2606 "license": "MIT", 2607 "bin": { ··· 2613 }, 2614 "node_modules/json-buffer": { 2615 "version": "3.0.1", 2616 - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 2617 - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 2618 "dev": true, 2619 "license": "MIT" 2620 }, 2621 "node_modules/json-schema-traverse": { 2622 - "version": "0.4.1", 2623 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 2624 - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 2625 "dev": true, 2626 "license": "MIT" 2627 }, 2628 "node_modules/json-stable-stringify-without-jsonify": { 2629 "version": "1.0.1", 2630 - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 2631 - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 2632 "dev": true, 2633 "license": "MIT" 2634 }, 2635 "node_modules/json5": { 2636 "version": "2.2.3", 2637 - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2638 - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2639 "dev": true, 2640 "license": "MIT", 2641 "bin": { ··· 2647 }, 2648 "node_modules/jsonfile": { 2649 "version": "6.2.0", 2650 - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", 2651 - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", 2652 "dev": true, 2653 "license": "MIT", 2654 "dependencies": { ··· 2660 }, 2661 "node_modules/keyv": { 2662 "version": "4.5.4", 2663 - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 2664 - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 2665 "dev": true, 2666 "license": "MIT", 2667 "dependencies": { ··· 2670 }, 2671 "node_modules/kolorist": { 2672 "version": "1.8.0", 2673 - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", 2674 - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", 2675 "dev": true, 2676 "license": "MIT" 2677 }, 2678 "node_modules/levn": { 2679 "version": "0.4.1", 2680 - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 2681 - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 2682 "dev": true, 2683 "license": "MIT", 2684 "dependencies": { ··· 2691 }, 2692 "node_modules/lightningcss": { 2693 "version": "1.30.2", 2694 - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", 2695 - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", 2696 "dev": true, 2697 "license": "MPL-2.0", 2698 "dependencies": { ··· 2719 "lightningcss-win32-x64-msvc": "1.30.2" 2720 } 2721 }, 2722 "node_modules/lightningcss-darwin-arm64": { 2723 "version": "1.30.2", 2724 "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", ··· 2740 "url": "https://opencollective.com/parcel" 2741 } 2742 }, 2743 "node_modules/local-pkg": { 2744 "version": "1.1.2", 2745 - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", 2746 - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", 2747 "dev": true, 2748 "license": "MIT", 2749 "dependencies": { ··· 2760 }, 2761 "node_modules/locate-path": { 2762 "version": "6.0.0", 2763 - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 2764 - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 2765 "dev": true, 2766 "license": "MIT", 2767 "dependencies": { ··· 2776 }, 2777 "node_modules/lodash": { 2778 "version": "4.17.21", 2779 - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 2780 - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 2781 "dev": true, 2782 "license": "MIT" 2783 }, 2784 "node_modules/lodash.merge": { 2785 "version": "4.6.2", 2786 - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 2787 - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 2788 "dev": true, 2789 "license": "MIT" 2790 }, 2791 "node_modules/lru-cache": { 2792 - "version": "5.1.1", 2793 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2794 - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 2795 "dev": true, 2796 "license": "ISC", 2797 "dependencies": { 2798 - "yallist": "^3.0.2" 2799 } 2800 }, 2801 "node_modules/magic-string": { 2802 - "version": "0.30.19", 2803 - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", 2804 - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", 2805 "dev": true, 2806 "license": "MIT", 2807 "dependencies": { ··· 2810 }, 2811 "node_modules/make-dir": { 2812 "version": "3.1.0", 2813 - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 2814 - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 2815 "dev": true, 2816 "license": "MIT", 2817 "dependencies": { ··· 2824 "url": "https://github.com/sponsors/sindresorhus" 2825 } 2826 }, 2827 - "node_modules/merge2": { 2828 - "version": "1.4.1", 2829 - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 2830 - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 2831 "dev": true, 2832 - "license": "MIT", 2833 - "engines": { 2834 - "node": ">= 8" 2835 - } 2836 - }, 2837 - "node_modules/micromatch": { 2838 - "version": "4.0.8", 2839 - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 2840 - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 2841 - "dev": true, 2842 - "license": "MIT", 2843 - "dependencies": { 2844 - "braces": "^3.0.3", 2845 - "picomatch": "^2.3.1" 2846 - }, 2847 - "engines": { 2848 - "node": ">=8.6" 2849 } 2850 }, 2851 "node_modules/minimatch": { 2852 - "version": "3.1.2", 2853 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 2854 - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 2855 "dev": true, 2856 "license": "ISC", 2857 "dependencies": { 2858 - "brace-expansion": "^1.1.7" 2859 }, 2860 "engines": { 2861 - "node": "*" 2862 } 2863 }, 2864 "node_modules/mlly": { 2865 "version": "1.8.0", 2866 - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", 2867 - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", 2868 "dev": true, 2869 "license": "MIT", 2870 "dependencies": { ··· 2874 "ufo": "^1.6.1" 2875 } 2876 }, 2877 - "node_modules/mlly/node_modules/confbox": { 2878 - "version": "0.1.8", 2879 - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", 2880 - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", 2881 - "dev": true, 2882 - "license": "MIT" 2883 - }, 2884 "node_modules/mlly/node_modules/pkg-types": { 2885 "version": "1.3.1", 2886 - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", 2887 - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", 2888 "dev": true, 2889 "license": "MIT", 2890 "dependencies": { ··· 2892 "mlly": "^1.7.4", 2893 "pathe": "^2.0.1" 2894 } 2895 }, 2896 "node_modules/ms": { 2897 "version": "2.1.3", 2898 - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2899 - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2900 "dev": true, 2901 "license": "MIT" 2902 }, 2903 "node_modules/nanoid": { 2904 "version": "3.3.11", 2905 - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2906 - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2907 "dev": true, 2908 "funding": [ 2909 { ··· 2921 }, 2922 "node_modules/natural-compare": { 2923 "version": "1.4.0", 2924 - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 2925 - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 2926 "dev": true, 2927 "license": "MIT" 2928 }, 2929 "node_modules/node-releases": { 2930 - "version": "2.0.23", 2931 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", 2932 - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", 2933 "dev": true, 2934 "license": "MIT" 2935 }, 2936 "node_modules/optionator": { 2937 "version": "0.9.4", 2938 - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 2939 - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 2940 "dev": true, 2941 "license": "MIT", 2942 "dependencies": { ··· 2953 }, 2954 "node_modules/p-limit": { 2955 "version": "3.1.0", 2956 - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 2957 - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 2958 "dev": true, 2959 "license": "MIT", 2960 "dependencies": { ··· 2969 }, 2970 "node_modules/p-locate": { 2971 "version": "5.0.0", 2972 - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 2973 - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 2974 "dev": true, 2975 "license": "MIT", 2976 "dependencies": { ··· 2985 }, 2986 "node_modules/p-try": { 2987 "version": "2.2.0", 2988 - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 2989 - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 2990 "dev": true, 2991 "license": "MIT", 2992 "engines": { ··· 2995 }, 2996 "node_modules/parent-module": { 2997 "version": "1.0.1", 2998 - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 2999 - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 3000 "dev": true, 3001 "license": "MIT", 3002 "dependencies": { ··· 3008 }, 3009 "node_modules/path-browserify": { 3010 "version": "1.0.1", 3011 - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 3012 - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 3013 "dev": true, 3014 "license": "MIT" 3015 }, 3016 "node_modules/path-exists": { 3017 "version": "4.0.0", 3018 - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 3019 - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 3020 "dev": true, 3021 "license": "MIT", 3022 "engines": { ··· 3025 }, 3026 "node_modules/path-key": { 3027 "version": "3.1.1", 3028 - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 3029 - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 3030 "dev": true, 3031 "license": "MIT", 3032 "engines": { ··· 3035 }, 3036 "node_modules/path-parse": { 3037 "version": "1.0.7", 3038 - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 3039 - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 3040 "dev": true, 3041 "license": "MIT" 3042 }, 3043 "node_modules/pathe": { 3044 "version": "2.0.3", 3045 - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 3046 - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 3047 "dev": true, 3048 "license": "MIT" 3049 }, 3050 "node_modules/picocolors": { 3051 "version": "1.1.1", 3052 - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 3053 - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 3054 "dev": true, 3055 "license": "ISC" 3056 }, 3057 "node_modules/picomatch": { 3058 - "version": "2.3.1", 3059 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 3060 - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 3061 "dev": true, 3062 "license": "MIT", 3063 "engines": { 3064 - "node": ">=8.6" 3065 }, 3066 "funding": { 3067 "url": "https://github.com/sponsors/jonschlinkert" ··· 3069 }, 3070 "node_modules/pkg-dir": { 3071 "version": "4.2.0", 3072 - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 3073 - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 3074 "dev": true, 3075 "license": "MIT", 3076 "dependencies": { ··· 3082 }, 3083 "node_modules/pkg-dir/node_modules/find-up": { 3084 "version": "4.1.0", 3085 - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 3086 - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 3087 "dev": true, 3088 "license": "MIT", 3089 "dependencies": { ··· 3094 "node": ">=8" 3095 } 3096 }, 3097 - "node_modules/pkg-dir/node_modules/locate-path": { 3098 "version": "5.0.0", 3099 - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 3100 - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 3101 "dev": true, 3102 "license": "MIT", 3103 "dependencies": { ··· 3107 "node": ">=8" 3108 } 3109 }, 3110 - "node_modules/pkg-dir/node_modules/p-limit": { 3111 - "version": "2.3.0", 3112 - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 3113 - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 3114 "dev": true, 3115 "license": "MIT", 3116 "dependencies": { 3117 - "p-try": "^2.0.0" 3118 }, 3119 "engines": { 3120 - "node": ">=6" 3121 - }, 3122 - "funding": { 3123 - "url": "https://github.com/sponsors/sindresorhus" 3124 } 3125 }, 3126 - "node_modules/pkg-dir/node_modules/p-locate": { 3127 - "version": "4.1.0", 3128 - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 3129 - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 3130 "dev": true, 3131 "license": "MIT", 3132 "dependencies": { 3133 - "p-limit": "^2.2.0" 3134 }, 3135 "engines": { 3136 - "node": ">=8" 3137 } 3138 }, 3139 "node_modules/pkg-types": { 3140 "version": "2.3.0", 3141 - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", 3142 - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", 3143 "dev": true, 3144 "license": "MIT", 3145 "dependencies": { ··· 3150 }, 3151 "node_modules/postcss": { 3152 "version": "8.5.6", 3153 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 3154 - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 3155 "dev": true, 3156 "funding": [ 3157 { ··· 3179 }, 3180 "node_modules/prelude-ls": { 3181 "version": "1.2.1", 3182 - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 3183 - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 3184 "dev": true, 3185 "license": "MIT", 3186 "engines": { ··· 3189 }, 3190 "node_modules/punycode": { 3191 "version": "2.3.1", 3192 - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 3193 - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 3194 "dev": true, 3195 "license": "MIT", 3196 "engines": { ··· 3199 }, 3200 "node_modules/quansync": { 3201 "version": "0.2.11", 3202 - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", 3203 - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", 3204 "dev": true, 3205 "funding": [ 3206 { ··· 3214 ], 3215 "license": "MIT" 3216 }, 3217 - "node_modules/queue-microtask": { 3218 - "version": "1.2.3", 3219 - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 3220 - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 3221 - "dev": true, 3222 - "funding": [ 3223 - { 3224 - "type": "github", 3225 - "url": "https://github.com/sponsors/feross" 3226 - }, 3227 - { 3228 - "type": "patreon", 3229 - "url": "https://www.patreon.com/feross" 3230 - }, 3231 - { 3232 - "type": "consulting", 3233 - "url": "https://feross.org/support" 3234 - } 3235 - ], 3236 - "license": "MIT" 3237 - }, 3238 "node_modules/react": { 3239 "version": "19.2.0", 3240 - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", 3241 - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", 3242 "dev": true, 3243 "license": "MIT", 3244 "engines": { 3245 "node": ">=0.10.0" 3246 } 3247 }, 3248 "node_modules/react-dom": { 3249 "version": "19.2.0", 3250 - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", 3251 - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", 3252 "dev": true, 3253 "license": "MIT", 3254 "dependencies": { ··· 3259 } 3260 }, 3261 "node_modules/react-refresh": { 3262 - "version": "0.17.0", 3263 - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", 3264 - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", 3265 "dev": true, 3266 "license": "MIT", 3267 "engines": { ··· 3270 }, 3271 "node_modules/require-from-string": { 3272 "version": "2.0.2", 3273 - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 3274 - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 3275 "dev": true, 3276 "license": "MIT", 3277 "engines": { ··· 3279 } 3280 }, 3281 "node_modules/resolve": { 3282 - "version": "1.22.10", 3283 - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", 3284 - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", 3285 "dev": true, 3286 "license": "MIT", 3287 "dependencies": { 3288 - "is-core-module": "^2.16.0", 3289 "path-parse": "^1.0.7", 3290 "supports-preserve-symlinks-flag": "^1.0.0" 3291 }, ··· 3301 }, 3302 "node_modules/resolve-from": { 3303 "version": "4.0.0", 3304 - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 3305 - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 3306 "dev": true, 3307 "license": "MIT", 3308 "engines": { 3309 "node": ">=4" 3310 } 3311 }, 3312 - "node_modules/reusify": { 3313 - "version": "1.1.0", 3314 - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 3315 - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 3316 - "dev": true, 3317 - "license": "MIT", 3318 - "engines": { 3319 - "iojs": ">=1.0.0", 3320 - "node": ">=0.10.0" 3321 - } 3322 - }, 3323 "node_modules/rolldown": { 3324 "version": "1.0.0-beta.41", 3325 - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.41.tgz", 3326 - "integrity": "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==", 3327 "dev": true, 3328 "license": "MIT", 3329 "dependencies": { 3330 "@oxc-project/types": "=0.93.0", 3331 "@rolldown/pluginutils": "1.0.0-beta.41", ··· 3356 }, 3357 "node_modules/rolldown/node_modules/@rolldown/pluginutils": { 3358 "version": "1.0.0-beta.41", 3359 - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.41.tgz", 3360 - "integrity": "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==", 3361 "dev": true, 3362 "license": "MIT" 3363 }, 3364 "node_modules/rollup": { 3365 - "version": "4.52.4", 3366 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", 3367 - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", 3368 "dev": true, 3369 "license": "MIT", 3370 "peer": true, ··· 3379 "npm": ">=8.0.0" 3380 }, 3381 "optionalDependencies": { 3382 - "@rollup/rollup-android-arm-eabi": "4.52.4", 3383 - "@rollup/rollup-android-arm64": "4.52.4", 3384 - "@rollup/rollup-darwin-arm64": "4.52.4", 3385 - "@rollup/rollup-darwin-x64": "4.52.4", 3386 - "@rollup/rollup-freebsd-arm64": "4.52.4", 3387 - "@rollup/rollup-freebsd-x64": "4.52.4", 3388 - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", 3389 - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", 3390 - "@rollup/rollup-linux-arm64-gnu": "4.52.4", 3391 - "@rollup/rollup-linux-arm64-musl": "4.52.4", 3392 - "@rollup/rollup-linux-loong64-gnu": "4.52.4", 3393 - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", 3394 - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", 3395 - "@rollup/rollup-linux-riscv64-musl": "4.52.4", 3396 - "@rollup/rollup-linux-s390x-gnu": "4.52.4", 3397 - "@rollup/rollup-linux-x64-gnu": "4.52.4", 3398 - "@rollup/rollup-linux-x64-musl": "4.52.4", 3399 - "@rollup/rollup-openharmony-arm64": "4.52.4", 3400 - "@rollup/rollup-win32-arm64-msvc": "4.52.4", 3401 - "@rollup/rollup-win32-ia32-msvc": "4.52.4", 3402 - "@rollup/rollup-win32-x64-gnu": "4.52.4", 3403 - "@rollup/rollup-win32-x64-msvc": "4.52.4", 3404 "fsevents": "~2.3.2" 3405 } 3406 }, 3407 "node_modules/rollup-plugin-typescript2": { 3408 "version": "0.36.0", 3409 - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz", 3410 - "integrity": "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==", 3411 "dev": true, 3412 "license": "MIT", 3413 "dependencies": { ··· 3422 "typescript": ">=2.4.0" 3423 } 3424 }, 3425 - "node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": { 3426 - "version": "4.2.1", 3427 - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 3428 - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 3429 - "dev": true, 3430 - "license": "MIT", 3431 - "dependencies": { 3432 - "estree-walker": "^2.0.1", 3433 - "picomatch": "^2.2.2" 3434 - }, 3435 - "engines": { 3436 - "node": ">= 8.0.0" 3437 - } 3438 - }, 3439 - "node_modules/rollup-plugin-typescript2/node_modules/fs-extra": { 3440 - "version": "10.1.0", 3441 - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 3442 - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 3443 - "dev": true, 3444 - "license": "MIT", 3445 - "dependencies": { 3446 - "graceful-fs": "^4.2.0", 3447 - "jsonfile": "^6.0.1", 3448 - "universalify": "^2.0.0" 3449 - }, 3450 - "engines": { 3451 - "node": ">=12" 3452 - } 3453 - }, 3454 "node_modules/rollup-plugin-typescript2/node_modules/semver": { 3455 "version": "7.7.3", 3456 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 3457 - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 3458 "dev": true, 3459 "license": "ISC", 3460 "bin": { ··· 3464 "node": ">=10" 3465 } 3466 }, 3467 - "node_modules/run-parallel": { 3468 - "version": "1.2.0", 3469 - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 3470 - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 3471 - "dev": true, 3472 - "funding": [ 3473 - { 3474 - "type": "github", 3475 - "url": "https://github.com/sponsors/feross" 3476 - }, 3477 - { 3478 - "type": "patreon", 3479 - "url": "https://www.patreon.com/feross" 3480 - }, 3481 - { 3482 - "type": "consulting", 3483 - "url": "https://feross.org/support" 3484 - } 3485 - ], 3486 - "license": "MIT", 3487 - "dependencies": { 3488 - "queue-microtask": "^1.2.2" 3489 - } 3490 - }, 3491 "node_modules/scheduler": { 3492 "version": "0.27.0", 3493 - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", 3494 - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", 3495 "dev": true, 3496 "license": "MIT" 3497 }, 3498 "node_modules/semver": { 3499 - "version": "6.3.1", 3500 - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 3501 - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 3502 "dev": true, 3503 "license": "ISC", 3504 "bin": { 3505 "semver": "bin/semver.js" 3506 } 3507 }, 3508 "node_modules/shebang-command": { 3509 "version": "2.0.0", 3510 - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 3511 - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 3512 "dev": true, 3513 "license": "MIT", 3514 "dependencies": { ··· 3520 }, 3521 "node_modules/shebang-regex": { 3522 "version": "3.0.0", 3523 - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 3524 - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 3525 "dev": true, 3526 "license": "MIT", 3527 "engines": { ··· 3530 }, 3531 "node_modules/source-map": { 3532 "version": "0.6.1", 3533 - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 3534 - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 3535 "dev": true, 3536 "license": "BSD-3-Clause", 3537 "engines": { ··· 3540 }, 3541 "node_modules/source-map-js": { 3542 "version": "1.2.1", 3543 - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3544 - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 3545 "dev": true, 3546 "license": "BSD-3-Clause", 3547 "engines": { ··· 3550 }, 3551 "node_modules/source-map-support": { 3552 "version": "0.5.21", 3553 - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 3554 - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 3555 "dev": true, 3556 "license": "MIT", 3557 "optional": true, 3558 - "peer": true, 3559 "dependencies": { 3560 "buffer-from": "^1.0.0", 3561 "source-map": "^0.6.0" ··· 3563 }, 3564 "node_modules/sprintf-js": { 3565 "version": "1.0.3", 3566 - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 3567 - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 3568 "dev": true, 3569 "license": "BSD-3-Clause" 3570 }, 3571 "node_modules/string-argv": { 3572 "version": "0.3.2", 3573 - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 3574 - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 3575 "dev": true, 3576 "license": "MIT", 3577 "engines": { ··· 3580 }, 3581 "node_modules/strip-json-comments": { 3582 "version": "3.1.1", 3583 - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 3584 - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 3585 "dev": true, 3586 "license": "MIT", 3587 "engines": { ··· 3592 } 3593 }, 3594 "node_modules/supports-color": { 3595 - "version": "7.2.0", 3596 - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 3597 - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 3598 "dev": true, 3599 "license": "MIT", 3600 "dependencies": { 3601 "has-flag": "^4.0.0" 3602 }, 3603 "engines": { 3604 - "node": ">=8" 3605 } 3606 }, 3607 "node_modules/supports-preserve-symlinks-flag": { 3608 "version": "1.0.0", 3609 - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 3610 - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 3611 "dev": true, 3612 "license": "MIT", 3613 "engines": { ··· 3617 "url": "https://github.com/sponsors/ljharb" 3618 } 3619 }, 3620 - "node_modules/terser": { 3621 - "version": "5.44.0", 3622 - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", 3623 - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", 3624 - "dev": true, 3625 - "license": "BSD-2-Clause", 3626 - "optional": true, 3627 - "peer": true, 3628 - "dependencies": { 3629 - "@jridgewell/source-map": "^0.3.3", 3630 - "acorn": "^8.15.0", 3631 - "commander": "^2.20.0", 3632 - "source-map-support": "~0.5.20" 3633 - }, 3634 - "bin": { 3635 - "terser": "bin/terser" 3636 - }, 3637 - "engines": { 3638 - "node": ">=10" 3639 - } 3640 - }, 3641 "node_modules/tinyglobby": { 3642 "version": "0.2.15", 3643 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 3644 - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 3645 "dev": true, 3646 "license": "MIT", 3647 "dependencies": { ··· 3655 "url": "https://github.com/sponsors/SuperchupuDev" 3656 } 3657 }, 3658 - "node_modules/tinyglobby/node_modules/fdir": { 3659 - "version": "6.5.0", 3660 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 3661 - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 3662 - "dev": true, 3663 - "license": "MIT", 3664 - "engines": { 3665 - "node": ">=12.0.0" 3666 - }, 3667 - "peerDependencies": { 3668 - "picomatch": "^3 || ^4" 3669 - }, 3670 - "peerDependenciesMeta": { 3671 - "picomatch": { 3672 - "optional": true 3673 - } 3674 - } 3675 - }, 3676 - "node_modules/tinyglobby/node_modules/picomatch": { 3677 - "version": "4.0.3", 3678 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 3679 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 3680 - "dev": true, 3681 - "license": "MIT", 3682 - "engines": { 3683 - "node": ">=12" 3684 - }, 3685 - "funding": { 3686 - "url": "https://github.com/sponsors/jonschlinkert" 3687 - } 3688 - }, 3689 - "node_modules/to-regex-range": { 3690 - "version": "5.0.1", 3691 - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 3692 - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 3693 - "dev": true, 3694 - "license": "MIT", 3695 - "dependencies": { 3696 - "is-number": "^7.0.0" 3697 - }, 3698 - "engines": { 3699 - "node": ">=8.0" 3700 - } 3701 - }, 3702 "node_modules/ts-api-utils": { 3703 "version": "2.1.0", 3704 - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", 3705 - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", 3706 "dev": true, 3707 "license": "MIT", 3708 "engines": { ··· 3714 }, 3715 "node_modules/tslib": { 3716 "version": "2.8.1", 3717 - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 3718 - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 3719 "dev": true, 3720 "license": "0BSD" 3721 }, 3722 "node_modules/type-check": { 3723 "version": "0.4.0", 3724 - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 3725 - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 3726 "dev": true, 3727 "license": "MIT", 3728 "dependencies": { ··· 3738 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3739 "dev": true, 3740 "license": "Apache-2.0", 3741 "bin": { 3742 "tsc": "bin/tsc", 3743 "tsserver": "bin/tsserver" ··· 3747 } 3748 }, 3749 "node_modules/typescript-eslint": { 3750 - "version": "8.46.0", 3751 - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.0.tgz", 3752 - "integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==", 3753 "dev": true, 3754 "license": "MIT", 3755 "dependencies": { 3756 - "@typescript-eslint/eslint-plugin": "8.46.0", 3757 - "@typescript-eslint/parser": "8.46.0", 3758 - "@typescript-eslint/typescript-estree": "8.46.0", 3759 - "@typescript-eslint/utils": "8.46.0" 3760 }, 3761 "engines": { 3762 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 3772 }, 3773 "node_modules/ufo": { 3774 "version": "1.6.1", 3775 - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", 3776 - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", 3777 "dev": true, 3778 "license": "MIT" 3779 }, 3780 "node_modules/undici-types": { 3781 - "version": "7.14.0", 3782 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", 3783 - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", 3784 "dev": true, 3785 "license": "MIT" 3786 }, 3787 "node_modules/universalify": { 3788 "version": "2.0.1", 3789 - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 3790 - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 3791 "dev": true, 3792 "license": "MIT", 3793 "engines": { ··· 3795 } 3796 }, 3797 "node_modules/unplugin": { 3798 - "version": "2.3.10", 3799 - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", 3800 - "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", 3801 "dev": true, 3802 "license": "MIT", 3803 "dependencies": { ··· 3812 }, 3813 "node_modules/unplugin-dts": { 3814 "version": "1.0.0-beta.6", 3815 - "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.0.0-beta.6.tgz", 3816 - "integrity": "sha512-+xbFv5aVFtLZFNBAKI4+kXmd2h+T42/AaP8Bsp0YP/je/uOTN94Ame2Xt3e9isZS+Z7/hrLCLbsVJh+saqFMfQ==", 3817 "dev": true, 3818 "license": "MIT", 3819 "dependencies": { ··· 3864 } 3865 } 3866 }, 3867 - "node_modules/unplugin/node_modules/picomatch": { 3868 - "version": "4.0.3", 3869 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 3870 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 3871 "dev": true, 3872 "license": "MIT", 3873 "engines": { 3874 - "node": ">=12" 3875 }, 3876 - "funding": { 3877 - "url": "https://github.com/sponsors/jonschlinkert" 3878 } 3879 }, 3880 "node_modules/update-browserslist-db": { 3881 - "version": "1.1.3", 3882 - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", 3883 - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", 3884 "dev": true, 3885 "funding": [ 3886 { ··· 3910 }, 3911 "node_modules/uri-js": { 3912 "version": "4.4.1", 3913 - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 3914 - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 3915 "dev": true, 3916 "license": "BSD-2-Clause", 3917 "dependencies": { ··· 3921 "node_modules/vite": { 3922 "name": "rolldown-vite", 3923 "version": "7.1.14", 3924 - "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", 3925 - "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", 3926 "dev": true, 3927 "license": "MIT", 3928 "dependencies": { 3929 "@oxc-project/runtime": "0.92.0", 3930 "fdir": "^6.5.0", ··· 3995 } 3996 } 3997 }, 3998 - "node_modules/vite/node_modules/fdir": { 3999 - "version": "6.5.0", 4000 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 4001 - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 4002 - "dev": true, 4003 - "license": "MIT", 4004 - "engines": { 4005 - "node": ">=12.0.0" 4006 - }, 4007 - "peerDependencies": { 4008 - "picomatch": "^3 || ^4" 4009 - }, 4010 - "peerDependenciesMeta": { 4011 - "picomatch": { 4012 - "optional": true 4013 - } 4014 - } 4015 - }, 4016 - "node_modules/vite/node_modules/picomatch": { 4017 - "version": "4.0.3", 4018 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 4019 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 4020 - "dev": true, 4021 - "license": "MIT", 4022 - "engines": { 4023 - "node": ">=12" 4024 - }, 4025 - "funding": { 4026 - "url": "https://github.com/sponsors/jonschlinkert" 4027 - } 4028 - }, 4029 "node_modules/vscode-uri": { 4030 "version": "3.1.0", 4031 - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", 4032 - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", 4033 "dev": true, 4034 "license": "MIT" 4035 }, 4036 "node_modules/webpack-virtual-modules": { 4037 "version": "0.6.2", 4038 - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", 4039 - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", 4040 "dev": true, 4041 "license": "MIT" 4042 }, 4043 "node_modules/which": { 4044 "version": "2.0.2", 4045 - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 4046 - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 4047 "dev": true, 4048 "license": "ISC", 4049 "dependencies": { ··· 4058 }, 4059 "node_modules/word-wrap": { 4060 "version": "1.2.5", 4061 - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 4062 - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 4063 "dev": true, 4064 "license": "MIT", 4065 "engines": { ··· 4067 } 4068 }, 4069 "node_modules/yallist": { 4070 - "version": "3.1.1", 4071 - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 4072 - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 4073 "dev": true, 4074 "license": "ISC" 4075 }, 4076 "node_modules/yocto-queue": { 4077 "version": "0.1.0", 4078 - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 4079 - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 4080 "dev": true, 4081 "license": "MIT", 4082 "engines": {
··· 1 { 2 "name": "atproto-ui", 3 + "version": "0.12", 4 "lockfileVersion": 3, 5 "requires": true, 6 "packages": { 7 "": { 8 "name": "atproto-ui", 9 + "version": "0.12", 10 "dependencies": { 11 "@atcute/atproto": "^3.1.7", 12 "@atcute/bluesky": "^3.2.3", 13 "@atcute/client": "^4.0.3", 14 "@atcute/identity-resolver": "^1.1.3", 15 + "@atcute/tangled": "^1.0.10" 16 }, 17 "devDependencies": { 18 "@eslint/js": "^9.36.0", ··· 44 } 45 }, 46 "node_modules/@atcute/atproto": { 47 + "version": "3.1.9", 48 "license": "0BSD", 49 "dependencies": { 50 "@atcute/lexicons": "^1.2.2" 51 } 52 }, 53 "node_modules/@atcute/bluesky": { 54 + "version": "3.2.11", 55 "license": "0BSD", 56 "dependencies": { 57 + "@atcute/atproto": "^3.1.9", 58 + "@atcute/lexicons": "^1.2.5" 59 } 60 }, 61 "node_modules/@atcute/client": { 62 + "version": "4.1.0", 63 + "license": "0BSD", 64 "dependencies": { 65 + "@atcute/identity": "^1.1.3", 66 + "@atcute/lexicons": "^1.2.5" 67 } 68 }, 69 "node_modules/@atcute/identity": { 70 + "version": "1.1.3", 71 "license": "0BSD", 72 + "peer": true, 73 "dependencies": { 74 + "@atcute/lexicons": "^1.2.4", 75 + "@badrap/valita": "^0.4.6" 76 } 77 }, 78 "node_modules/@atcute/identity-resolver": { 79 "version": "1.1.4", 80 "license": "0BSD", 81 "dependencies": { 82 "@atcute/lexicons": "^1.2.2", ··· 88 } 89 }, 90 "node_modules/@atcute/lexicons": { 91 + "version": "1.2.5", 92 "license": "0BSD", 93 "dependencies": { 94 "@standard-schema/spec": "^1.0.0", ··· 96 } 97 }, 98 "node_modules/@atcute/tangled": { 99 + "version": "1.0.12", 100 "license": "0BSD", 101 "dependencies": { 102 + "@atcute/atproto": "^3.1.9", 103 + "@atcute/lexicons": "^1.2.3" 104 } 105 }, 106 "node_modules/@atcute/util-fetch": { 107 + "version": "1.0.4", 108 "license": "0BSD", 109 "dependencies": { 110 "@badrap/valita": "^0.4.6" ··· 112 }, 113 "node_modules/@babel/code-frame": { 114 "version": "7.27.1", 115 "dev": true, 116 "license": "MIT", 117 "dependencies": { ··· 124 } 125 }, 126 "node_modules/@babel/compat-data": { 127 + "version": "7.28.5", 128 "dev": true, 129 "license": "MIT", 130 "engines": { ··· 132 } 133 }, 134 "node_modules/@babel/core": { 135 + "version": "7.28.5", 136 "dev": true, 137 "license": "MIT", 138 + "peer": true, 139 "dependencies": { 140 "@babel/code-frame": "^7.27.1", 141 + "@babel/generator": "^7.28.5", 142 "@babel/helper-compilation-targets": "^7.27.2", 143 "@babel/helper-module-transforms": "^7.28.3", 144 "@babel/helpers": "^7.28.4", 145 + "@babel/parser": "^7.28.5", 146 "@babel/template": "^7.27.2", 147 + "@babel/traverse": "^7.28.5", 148 + "@babel/types": "^7.28.5", 149 "@jridgewell/remapping": "^2.3.5", 150 "convert-source-map": "^2.0.0", 151 "debug": "^4.1.0", ··· 161 "url": "https://opencollective.com/babel" 162 } 163 }, 164 + "node_modules/@babel/core/node_modules/semver": { 165 + "version": "6.3.1", 166 + "dev": true, 167 + "license": "ISC", 168 + "bin": { 169 + "semver": "bin/semver.js" 170 + } 171 + }, 172 "node_modules/@babel/generator": { 173 + "version": "7.28.5", 174 "dev": true, 175 "license": "MIT", 176 "dependencies": { 177 + "@babel/parser": "^7.28.5", 178 + "@babel/types": "^7.28.5", 179 "@jridgewell/gen-mapping": "^0.3.12", 180 "@jridgewell/trace-mapping": "^0.3.28", 181 "jsesc": "^3.0.2" ··· 186 }, 187 "node_modules/@babel/helper-compilation-targets": { 188 "version": "7.27.2", 189 "dev": true, 190 "license": "MIT", 191 "dependencies": { ··· 199 "node": ">=6.9.0" 200 } 201 }, 202 + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { 203 + "version": "5.1.1", 204 + "dev": true, 205 + "license": "ISC", 206 + "dependencies": { 207 + "yallist": "^3.0.2" 208 + } 209 + }, 210 + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/node_modules/yallist": { 211 + "version": "3.1.1", 212 + "dev": true, 213 + "license": "ISC" 214 + }, 215 + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { 216 + "version": "6.3.1", 217 + "dev": true, 218 + "license": "ISC", 219 + "bin": { 220 + "semver": "bin/semver.js" 221 + } 222 + }, 223 "node_modules/@babel/helper-globals": { 224 "version": "7.28.0", 225 "dev": true, 226 "license": "MIT", 227 "engines": { ··· 230 }, 231 "node_modules/@babel/helper-module-imports": { 232 "version": "7.27.1", 233 "dev": true, 234 "license": "MIT", 235 "dependencies": { ··· 242 }, 243 "node_modules/@babel/helper-module-transforms": { 244 "version": "7.28.3", 245 "dev": true, 246 "license": "MIT", 247 "dependencies": { ··· 258 }, 259 "node_modules/@babel/helper-plugin-utils": { 260 "version": "7.27.1", 261 "dev": true, 262 "license": "MIT", 263 "engines": { ··· 266 }, 267 "node_modules/@babel/helper-string-parser": { 268 "version": "7.27.1", 269 "dev": true, 270 "license": "MIT", 271 "engines": { ··· 273 } 274 }, 275 "node_modules/@babel/helper-validator-identifier": { 276 + "version": "7.28.5", 277 "dev": true, 278 "license": "MIT", 279 "engines": { ··· 282 }, 283 "node_modules/@babel/helper-validator-option": { 284 "version": "7.27.1", 285 "dev": true, 286 "license": "MIT", 287 "engines": { ··· 290 }, 291 "node_modules/@babel/helpers": { 292 "version": "7.28.4", 293 "dev": true, 294 "license": "MIT", 295 "dependencies": { ··· 301 } 302 }, 303 "node_modules/@babel/parser": { 304 + "version": "7.28.5", 305 "dev": true, 306 "license": "MIT", 307 "dependencies": { 308 + "@babel/types": "^7.28.5" 309 }, 310 "bin": { 311 "parser": "bin/babel-parser.js" ··· 316 }, 317 "node_modules/@babel/plugin-transform-react-jsx-self": { 318 "version": "7.27.1", 319 "dev": true, 320 "license": "MIT", 321 "dependencies": { ··· 330 }, 331 "node_modules/@babel/plugin-transform-react-jsx-source": { 332 "version": "7.27.1", 333 "dev": true, 334 "license": "MIT", 335 "dependencies": { ··· 344 }, 345 "node_modules/@babel/template": { 346 "version": "7.27.2", 347 "dev": true, 348 "license": "MIT", 349 "dependencies": { ··· 356 } 357 }, 358 "node_modules/@babel/traverse": { 359 + "version": "7.28.5", 360 "dev": true, 361 "license": "MIT", 362 "dependencies": { 363 "@babel/code-frame": "^7.27.1", 364 + "@babel/generator": "^7.28.5", 365 "@babel/helper-globals": "^7.28.0", 366 + "@babel/parser": "^7.28.5", 367 "@babel/template": "^7.27.2", 368 + "@babel/types": "^7.28.5", 369 "debug": "^4.3.1" 370 }, 371 "engines": { ··· 373 } 374 }, 375 "node_modules/@babel/types": { 376 + "version": "7.28.5", 377 "dev": true, 378 "license": "MIT", 379 "dependencies": { 380 "@babel/helper-string-parser": "^7.27.1", 381 + "@babel/helper-validator-identifier": "^7.28.5" 382 }, 383 "engines": { 384 "node": ">=6.9.0" ··· 386 }, 387 "node_modules/@badrap/valita": { 388 "version": "0.4.6", 389 "license": "MIT", 390 "engines": { 391 "node": ">= 18" 392 } 393 }, 394 + "node_modules/@emnapi/core": { 395 + "version": "1.7.1", 396 + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", 397 + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", 398 + "dev": true, 399 + "license": "MIT", 400 + "optional": true, 401 + "dependencies": { 402 + "@emnapi/wasi-threads": "1.1.0", 403 + "tslib": "^2.4.0" 404 + } 405 + }, 406 + "node_modules/@emnapi/runtime": { 407 + "version": "1.7.1", 408 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", 409 + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", 410 + "dev": true, 411 + "license": "MIT", 412 + "optional": true, 413 + "dependencies": { 414 + "tslib": "^2.4.0" 415 + } 416 + }, 417 + "node_modules/@emnapi/wasi-threads": { 418 + "version": "1.1.0", 419 + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", 420 + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", 421 + "dev": true, 422 + "license": "MIT", 423 + "optional": true, 424 + "dependencies": { 425 + "tslib": "^2.4.0" 426 + } 427 + }, 428 "node_modules/@eslint-community/eslint-utils": { 429 "version": "4.9.0", 430 "dev": true, 431 "license": "MIT", 432 "dependencies": { ··· 444 }, 445 "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 446 "version": "3.4.3", 447 "dev": true, 448 "license": "Apache-2.0", 449 "engines": { ··· 454 } 455 }, 456 "node_modules/@eslint-community/regexpp": { 457 + "version": "4.12.2", 458 "dev": true, 459 "license": "MIT", 460 "engines": { ··· 462 } 463 }, 464 "node_modules/@eslint/config-array": { 465 + "version": "0.21.1", 466 "dev": true, 467 "license": "Apache-2.0", 468 "dependencies": { 469 + "@eslint/object-schema": "^2.1.7", 470 "debug": "^4.3.1", 471 "minimatch": "^3.1.2" 472 }, ··· 474 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 475 } 476 }, 477 + "node_modules/@eslint/config-array/node_modules/minimatch": { 478 + "version": "3.1.2", 479 + "dev": true, 480 + "license": "ISC", 481 + "dependencies": { 482 + "brace-expansion": "^1.1.7" 483 + }, 484 + "engines": { 485 + "node": "*" 486 + } 487 + }, 488 "node_modules/@eslint/config-helpers": { 489 + "version": "0.4.2", 490 "dev": true, 491 "license": "Apache-2.0", 492 "dependencies": { 493 + "@eslint/core": "^0.17.0" 494 }, 495 "engines": { 496 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 497 } 498 }, 499 "node_modules/@eslint/core": { 500 + "version": "0.17.0", 501 "dev": true, 502 "license": "Apache-2.0", 503 "dependencies": { ··· 508 } 509 }, 510 "node_modules/@eslint/eslintrc": { 511 + "version": "3.3.3", 512 "dev": true, 513 "license": "MIT", 514 "dependencies": { ··· 518 "globals": "^14.0.0", 519 "ignore": "^5.2.0", 520 "import-fresh": "^3.2.1", 521 + "js-yaml": "^4.1.1", 522 "minimatch": "^3.1.2", 523 "strip-json-comments": "^3.1.1" 524 }, ··· 529 "url": "https://opencollective.com/eslint" 530 } 531 }, 532 + "node_modules/@eslint/eslintrc/node_modules/ajv": { 533 + "version": "6.12.6", 534 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 535 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 536 + "dev": true, 537 + "license": "MIT", 538 + "dependencies": { 539 + "fast-deep-equal": "^3.1.1", 540 + "fast-json-stable-stringify": "^2.0.0", 541 + "json-schema-traverse": "^0.4.1", 542 + "uri-js": "^4.2.2" 543 + }, 544 + "funding": { 545 + "type": "github", 546 + "url": "https://github.com/sponsors/epoberezkin" 547 + } 548 + }, 549 "node_modules/@eslint/eslintrc/node_modules/globals": { 550 "version": "14.0.0", 551 "dev": true, 552 "license": "MIT", 553 "engines": { ··· 557 "url": "https://github.com/sponsors/sindresorhus" 558 } 559 }, 560 + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { 561 + "version": "0.4.1", 562 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 563 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 564 + "dev": true, 565 + "license": "MIT" 566 + }, 567 + "node_modules/@eslint/eslintrc/node_modules/minimatch": { 568 + "version": "3.1.2", 569 + "dev": true, 570 + "license": "ISC", 571 + "dependencies": { 572 + "brace-expansion": "^1.1.7" 573 + }, 574 + "engines": { 575 + "node": "*" 576 + } 577 + }, 578 "node_modules/@eslint/js": { 579 + "version": "9.39.1", 580 "dev": true, 581 "license": "MIT", 582 "engines": { ··· 587 } 588 }, 589 "node_modules/@eslint/object-schema": { 590 + "version": "2.1.7", 591 "dev": true, 592 "license": "Apache-2.0", 593 "engines": { ··· 595 } 596 }, 597 "node_modules/@eslint/plugin-kit": { 598 + "version": "0.4.1", 599 "dev": true, 600 "license": "Apache-2.0", 601 "dependencies": { 602 + "@eslint/core": "^0.17.0", 603 "levn": "^0.4.1" 604 }, 605 "engines": { ··· 608 }, 609 "node_modules/@humanfs/core": { 610 "version": "0.19.1", 611 "dev": true, 612 "license": "Apache-2.0", 613 "engines": { ··· 616 }, 617 "node_modules/@humanfs/node": { 618 "version": "0.16.7", 619 "dev": true, 620 "license": "Apache-2.0", 621 "dependencies": { ··· 628 }, 629 "node_modules/@humanwhocodes/module-importer": { 630 "version": "1.0.1", 631 "dev": true, 632 "license": "Apache-2.0", 633 "engines": { ··· 640 }, 641 "node_modules/@humanwhocodes/retry": { 642 "version": "0.4.3", 643 "dev": true, 644 "license": "Apache-2.0", 645 "engines": { ··· 652 }, 653 "node_modules/@isaacs/balanced-match": { 654 "version": "4.0.1", 655 "dev": true, 656 "license": "MIT", 657 "engines": { ··· 660 }, 661 "node_modules/@isaacs/brace-expansion": { 662 "version": "5.0.0", 663 "dev": true, 664 "license": "MIT", 665 "dependencies": { ··· 671 }, 672 "node_modules/@jridgewell/gen-mapping": { 673 "version": "0.3.13", 674 "dev": true, 675 "license": "MIT", 676 "dependencies": { ··· 680 }, 681 "node_modules/@jridgewell/remapping": { 682 "version": "2.3.5", 683 "dev": true, 684 "license": "MIT", 685 "dependencies": { ··· 689 }, 690 "node_modules/@jridgewell/resolve-uri": { 691 "version": "3.1.2", 692 "dev": true, 693 "license": "MIT", 694 "engines": { ··· 697 }, 698 "node_modules/@jridgewell/source-map": { 699 "version": "0.3.11", 700 "dev": true, 701 "license": "MIT", 702 "optional": true, 703 "dependencies": { 704 "@jridgewell/gen-mapping": "^0.3.5", 705 "@jridgewell/trace-mapping": "^0.3.25" ··· 707 }, 708 "node_modules/@jridgewell/sourcemap-codec": { 709 "version": "1.5.5", 710 "dev": true, 711 "license": "MIT" 712 }, 713 "node_modules/@jridgewell/trace-mapping": { 714 "version": "0.3.31", 715 "dev": true, 716 "license": "MIT", 717 "dependencies": { ··· 720 } 721 }, 722 "node_modules/@microsoft/api-extractor": { 723 + "version": "7.55.1", 724 "dev": true, 725 "license": "MIT", 726 "dependencies": { 727 + "@microsoft/api-extractor-model": "7.32.1", 728 + "@microsoft/tsdoc": "~0.16.0", 729 + "@microsoft/tsdoc-config": "~0.18.0", 730 + "@rushstack/node-core-library": "5.19.0", 731 "@rushstack/rig-package": "0.6.0", 732 + "@rushstack/terminal": "0.19.4", 733 + "@rushstack/ts-command-line": "5.1.4", 734 + "diff": "~8.0.2", 735 "lodash": "~4.17.15", 736 "minimatch": "10.0.3", 737 "resolve": "~1.22.1", ··· 744 } 745 }, 746 "node_modules/@microsoft/api-extractor-model": { 747 + "version": "7.32.1", 748 "dev": true, 749 "license": "MIT", 750 "dependencies": { 751 + "@microsoft/tsdoc": "~0.16.0", 752 + "@microsoft/tsdoc-config": "~0.18.0", 753 + "@rushstack/node-core-library": "5.19.0" 754 } 755 }, 756 "node_modules/@microsoft/api-extractor/node_modules/typescript": { 757 "version": "5.8.2", 758 "dev": true, 759 "license": "Apache-2.0", 760 "bin": { ··· 765 "node": ">=14.17" 766 } 767 }, 768 "node_modules/@microsoft/tsdoc": { 769 + "version": "0.16.0", 770 "dev": true, 771 "license": "MIT" 772 }, 773 "node_modules/@microsoft/tsdoc-config": { 774 + "version": "0.18.0", 775 "dev": true, 776 "license": "MIT", 777 "dependencies": { 778 + "@microsoft/tsdoc": "0.16.0", 779 "ajv": "~8.12.0", 780 "jju": "~1.4.0", 781 "resolve": "~1.22.2" ··· 783 }, 784 "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { 785 "version": "8.12.0", 786 "dev": true, 787 "license": "MIT", 788 "dependencies": { ··· 796 "url": "https://github.com/sponsors/epoberezkin" 797 } 798 }, 799 + "node_modules/@napi-rs/wasm-runtime": { 800 + "version": "1.1.0", 801 + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", 802 + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", 803 + "dev": true, 804 + "license": "MIT", 805 + "optional": true, 806 + "dependencies": { 807 + "@emnapi/core": "^1.7.1", 808 + "@emnapi/runtime": "^1.7.1", 809 + "@tybys/wasm-util": "^0.10.1" 810 + } 811 + }, 812 + "node_modules/@oxc-project/runtime": { 813 + "version": "0.92.0", 814 + "dev": true, 815 + "license": "MIT", 816 + "engines": { 817 + "node": "^20.19.0 || >=22.12.0" 818 + } 819 + }, 820 + "node_modules/@oxc-project/types": { 821 + "version": "0.93.0", 822 + "dev": true, 823 + "license": "MIT", 824 + "funding": { 825 + "url": "https://github.com/sponsors/Boshen" 826 + } 827 + }, 828 + "node_modules/@rolldown/binding-android-arm64": { 829 + "version": "1.0.0-beta.41", 830 + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", 831 + "integrity": "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==", 832 + "cpu": [ 833 + "arm64" 834 + ], 835 + "dev": true, 836 + "license": "MIT", 837 + "optional": true, 838 + "os": [ 839 + "android" 840 + ], 841 + "engines": { 842 + "node": "^20.19.0 || >=22.12.0" 843 + } 844 + }, 845 + "node_modules/@rolldown/binding-darwin-arm64": { 846 + "version": "1.0.0-beta.41", 847 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz", 848 + "integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==", 849 + "cpu": [ 850 + "arm64" 851 + ], 852 + "dev": true, 853 + "license": "MIT", 854 + "optional": true, 855 + "os": [ 856 + "darwin" 857 + ], 858 + "engines": { 859 + "node": "^20.19.0 || >=22.12.0" 860 + } 861 + }, 862 + "node_modules/@rolldown/binding-darwin-x64": { 863 + "version": "1.0.0-beta.41", 864 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.41.tgz", 865 + "integrity": "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==", 866 + "cpu": [ 867 + "x64" 868 + ], 869 + "dev": true, 870 + "license": "MIT", 871 + "optional": true, 872 + "os": [ 873 + "darwin" 874 + ], 875 + "engines": { 876 + "node": "^20.19.0 || >=22.12.0" 877 + } 878 + }, 879 + "node_modules/@rolldown/binding-freebsd-x64": { 880 + "version": "1.0.0-beta.41", 881 + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.41.tgz", 882 + "integrity": "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==", 883 + "cpu": [ 884 + "x64" 885 + ], 886 + "dev": true, 887 + "license": "MIT", 888 + "optional": true, 889 + "os": [ 890 + "freebsd" 891 + ], 892 + "engines": { 893 + "node": "^20.19.0 || >=22.12.0" 894 + } 895 + }, 896 + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { 897 + "version": "1.0.0-beta.41", 898 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.41.tgz", 899 + "integrity": "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==", 900 + "cpu": [ 901 + "arm" 902 + ], 903 + "dev": true, 904 + "license": "MIT", 905 + "optional": true, 906 + "os": [ 907 + "linux" 908 + ], 909 + "engines": { 910 + "node": "^20.19.0 || >=22.12.0" 911 + } 912 + }, 913 + "node_modules/@rolldown/binding-linux-arm64-gnu": { 914 + "version": "1.0.0-beta.41", 915 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.41.tgz", 916 + "integrity": "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==", 917 + "cpu": [ 918 + "arm64" 919 + ], 920 + "dev": true, 921 + "license": "MIT", 922 + "optional": true, 923 + "os": [ 924 + "linux" 925 + ], 926 + "engines": { 927 + "node": "^20.19.0 || >=22.12.0" 928 + } 929 + }, 930 + "node_modules/@rolldown/binding-linux-arm64-musl": { 931 + "version": "1.0.0-beta.41", 932 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.41.tgz", 933 + "integrity": "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==", 934 + "cpu": [ 935 + "arm64" 936 + ], 937 "dev": true, 938 + "license": "MIT", 939 + "optional": true, 940 + "os": [ 941 + "linux" 942 + ], 943 + "engines": { 944 + "node": "^20.19.0 || >=22.12.0" 945 + } 946 }, 947 + "node_modules/@rolldown/binding-linux-x64-gnu": { 948 + "version": "1.0.0-beta.41", 949 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.41.tgz", 950 + "integrity": "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==", 951 + "cpu": [ 952 + "x64" 953 + ], 954 "dev": true, 955 "license": "MIT", 956 + "optional": true, 957 + "os": [ 958 + "linux" 959 + ], 960 "engines": { 961 + "node": "^20.19.0 || >=22.12.0" 962 } 963 }, 964 + "node_modules/@rolldown/binding-linux-x64-musl": { 965 + "version": "1.0.0-beta.41", 966 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.41.tgz", 967 + "integrity": "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==", 968 + "cpu": [ 969 + "x64" 970 + ], 971 "dev": true, 972 "license": "MIT", 973 + "optional": true, 974 + "os": [ 975 + "linux" 976 + ], 977 "engines": { 978 + "node": "^20.19.0 || >=22.12.0" 979 } 980 }, 981 + "node_modules/@rolldown/binding-openharmony-arm64": { 982 + "version": "1.0.0-beta.41", 983 + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.41.tgz", 984 + "integrity": "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==", 985 + "cpu": [ 986 + "arm64" 987 + ], 988 + "dev": true, 989 + "license": "MIT", 990 + "optional": true, 991 + "os": [ 992 + "openharmony" 993 + ], 994 + "engines": { 995 + "node": "^20.19.0 || >=22.12.0" 996 + } 997 + }, 998 + "node_modules/@rolldown/binding-wasm32-wasi": { 999 + "version": "1.0.0-beta.41", 1000 + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.41.tgz", 1001 + "integrity": "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==", 1002 + "cpu": [ 1003 + "wasm32" 1004 + ], 1005 "dev": true, 1006 "license": "MIT", 1007 + "optional": true, 1008 "dependencies": { 1009 + "@napi-rs/wasm-runtime": "^1.0.5" 1010 }, 1011 "engines": { 1012 + "node": ">=14.0.0" 1013 } 1014 }, 1015 + "node_modules/@rolldown/binding-win32-arm64-msvc": { 1016 + "version": "1.0.0-beta.41", 1017 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz", 1018 + "integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==", 1019 + "cpu": [ 1020 + "arm64" 1021 + ], 1022 "dev": true, 1023 "license": "MIT", 1024 + "optional": true, 1025 + "os": [ 1026 + "win32" 1027 + ], 1028 "engines": { 1029 "node": "^20.19.0 || >=22.12.0" 1030 } 1031 }, 1032 + "node_modules/@rolldown/binding-win32-ia32-msvc": { 1033 + "version": "1.0.0-beta.41", 1034 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz", 1035 + "integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==", 1036 + "cpu": [ 1037 + "ia32" 1038 + ], 1039 "dev": true, 1040 "license": "MIT", 1041 + "optional": true, 1042 + "os": [ 1043 + "win32" 1044 + ], 1045 + "engines": { 1046 + "node": "^20.19.0 || >=22.12.0" 1047 } 1048 }, 1049 + "node_modules/@rolldown/binding-win32-x64-msvc": { 1050 "version": "1.0.0-beta.41", 1051 "cpu": [ 1052 + "x64" 1053 ], 1054 "dev": true, 1055 "license": "MIT", 1056 "optional": true, 1057 "os": [ 1058 + "win32" 1059 ], 1060 "engines": { 1061 "node": "^20.19.0 || >=22.12.0" 1062 } 1063 }, 1064 "node_modules/@rolldown/pluginutils": { 1065 + "version": "1.0.0-beta.47", 1066 "dev": true, 1067 "license": "MIT" 1068 }, 1069 "node_modules/@rollup/pluginutils": { 1070 + "version": "4.2.1", 1071 "dev": true, 1072 "license": "MIT", 1073 "dependencies": { 1074 + "estree-walker": "^2.0.1", 1075 + "picomatch": "^2.2.2" 1076 }, 1077 "engines": { 1078 + "node": ">= 8.0.0" 1079 } 1080 }, 1081 "node_modules/@rollup/pluginutils/node_modules/picomatch": { 1082 + "version": "2.3.1", 1083 "dev": true, 1084 "license": "MIT", 1085 "engines": { 1086 + "node": ">=8.6" 1087 }, 1088 "funding": { 1089 "url": "https://github.com/sponsors/jonschlinkert" 1090 } 1091 }, 1092 + "node_modules/@rollup/rollup-android-arm-eabi": { 1093 + "version": "4.53.3", 1094 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", 1095 + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", 1096 + "cpu": [ 1097 + "arm" 1098 + ], 1099 + "dev": true, 1100 + "license": "MIT", 1101 + "optional": true, 1102 + "os": [ 1103 + "android" 1104 + ] 1105 + }, 1106 + "node_modules/@rollup/rollup-android-arm64": { 1107 + "version": "4.53.3", 1108 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", 1109 + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", 1110 + "cpu": [ 1111 + "arm64" 1112 + ], 1113 + "dev": true, 1114 + "license": "MIT", 1115 + "optional": true, 1116 + "os": [ 1117 + "android" 1118 + ] 1119 + }, 1120 "node_modules/@rollup/rollup-darwin-arm64": { 1121 + "version": "4.53.3", 1122 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", 1123 + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", 1124 "cpu": [ 1125 "arm64" 1126 ], ··· 1129 "optional": true, 1130 "os": [ 1131 "darwin" 1132 + ] 1133 + }, 1134 + "node_modules/@rollup/rollup-darwin-x64": { 1135 + "version": "4.53.3", 1136 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", 1137 + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", 1138 + "cpu": [ 1139 + "x64" 1140 ], 1141 + "dev": true, 1142 + "license": "MIT", 1143 + "optional": true, 1144 + "os": [ 1145 + "darwin" 1146 + ] 1147 + }, 1148 + "node_modules/@rollup/rollup-freebsd-arm64": { 1149 + "version": "4.53.3", 1150 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", 1151 + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", 1152 + "cpu": [ 1153 + "arm64" 1154 + ], 1155 + "dev": true, 1156 + "license": "MIT", 1157 + "optional": true, 1158 + "os": [ 1159 + "freebsd" 1160 + ] 1161 + }, 1162 + "node_modules/@rollup/rollup-freebsd-x64": { 1163 + "version": "4.53.3", 1164 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", 1165 + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", 1166 + "cpu": [ 1167 + "x64" 1168 + ], 1169 + "dev": true, 1170 + "license": "MIT", 1171 + "optional": true, 1172 + "os": [ 1173 + "freebsd" 1174 + ] 1175 + }, 1176 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1177 + "version": "4.53.3", 1178 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", 1179 + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", 1180 + "cpu": [ 1181 + "arm" 1182 + ], 1183 + "dev": true, 1184 + "license": "MIT", 1185 + "optional": true, 1186 + "os": [ 1187 + "linux" 1188 + ] 1189 + }, 1190 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1191 + "version": "4.53.3", 1192 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", 1193 + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", 1194 + "cpu": [ 1195 + "arm" 1196 + ], 1197 + "dev": true, 1198 + "license": "MIT", 1199 + "optional": true, 1200 + "os": [ 1201 + "linux" 1202 + ] 1203 + }, 1204 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1205 + "version": "4.53.3", 1206 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", 1207 + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", 1208 + "cpu": [ 1209 + "arm64" 1210 + ], 1211 + "dev": true, 1212 + "license": "MIT", 1213 + "optional": true, 1214 + "os": [ 1215 + "linux" 1216 + ] 1217 + }, 1218 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1219 + "version": "4.53.3", 1220 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", 1221 + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", 1222 + "cpu": [ 1223 + "arm64" 1224 + ], 1225 + "dev": true, 1226 + "license": "MIT", 1227 + "optional": true, 1228 + "os": [ 1229 + "linux" 1230 + ] 1231 + }, 1232 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 1233 + "version": "4.53.3", 1234 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", 1235 + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", 1236 + "cpu": [ 1237 + "loong64" 1238 + ], 1239 + "dev": true, 1240 + "license": "MIT", 1241 + "optional": true, 1242 + "os": [ 1243 + "linux" 1244 + ] 1245 + }, 1246 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1247 + "version": "4.53.3", 1248 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", 1249 + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", 1250 + "cpu": [ 1251 + "ppc64" 1252 + ], 1253 + "dev": true, 1254 + "license": "MIT", 1255 + "optional": true, 1256 + "os": [ 1257 + "linux" 1258 + ] 1259 + }, 1260 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1261 + "version": "4.53.3", 1262 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", 1263 + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", 1264 + "cpu": [ 1265 + "riscv64" 1266 + ], 1267 + "dev": true, 1268 + "license": "MIT", 1269 + "optional": true, 1270 + "os": [ 1271 + "linux" 1272 + ] 1273 + }, 1274 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1275 + "version": "4.53.3", 1276 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", 1277 + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", 1278 + "cpu": [ 1279 + "riscv64" 1280 + ], 1281 + "dev": true, 1282 + "license": "MIT", 1283 + "optional": true, 1284 + "os": [ 1285 + "linux" 1286 + ] 1287 + }, 1288 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1289 + "version": "4.53.3", 1290 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", 1291 + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", 1292 + "cpu": [ 1293 + "s390x" 1294 + ], 1295 + "dev": true, 1296 + "license": "MIT", 1297 + "optional": true, 1298 + "os": [ 1299 + "linux" 1300 + ] 1301 + }, 1302 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1303 + "version": "4.53.3", 1304 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", 1305 + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", 1306 + "cpu": [ 1307 + "x64" 1308 + ], 1309 + "dev": true, 1310 + "license": "MIT", 1311 + "optional": true, 1312 + "os": [ 1313 + "linux" 1314 + ] 1315 + }, 1316 + "node_modules/@rollup/rollup-linux-x64-musl": { 1317 + "version": "4.53.3", 1318 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", 1319 + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", 1320 + "cpu": [ 1321 + "x64" 1322 + ], 1323 + "dev": true, 1324 + "license": "MIT", 1325 + "optional": true, 1326 + "os": [ 1327 + "linux" 1328 + ] 1329 + }, 1330 + "node_modules/@rollup/rollup-openharmony-arm64": { 1331 + "version": "4.53.3", 1332 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", 1333 + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", 1334 + "cpu": [ 1335 + "arm64" 1336 + ], 1337 + "dev": true, 1338 + "license": "MIT", 1339 + "optional": true, 1340 + "os": [ 1341 + "openharmony" 1342 + ] 1343 + }, 1344 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1345 + "version": "4.53.3", 1346 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", 1347 + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", 1348 + "cpu": [ 1349 + "arm64" 1350 + ], 1351 + "dev": true, 1352 + "license": "MIT", 1353 + "optional": true, 1354 + "os": [ 1355 + "win32" 1356 + ] 1357 + }, 1358 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1359 + "version": "4.53.3", 1360 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", 1361 + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", 1362 + "cpu": [ 1363 + "ia32" 1364 + ], 1365 + "dev": true, 1366 + "license": "MIT", 1367 + "optional": true, 1368 + "os": [ 1369 + "win32" 1370 + ] 1371 + }, 1372 + "node_modules/@rollup/rollup-win32-x64-gnu": { 1373 + "version": "4.53.3", 1374 + "cpu": [ 1375 + "x64" 1376 + ], 1377 + "dev": true, 1378 + "license": "MIT", 1379 + "optional": true, 1380 + "os": [ 1381 + "win32" 1382 + ] 1383 + }, 1384 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1385 + "version": "4.53.3", 1386 + "cpu": [ 1387 + "x64" 1388 + ], 1389 + "dev": true, 1390 + "license": "MIT", 1391 + "optional": true, 1392 + "os": [ 1393 + "win32" 1394 + ] 1395 }, 1396 "node_modules/@rushstack/node-core-library": { 1397 + "version": "5.19.0", 1398 "dev": true, 1399 "license": "MIT", 1400 "dependencies": { ··· 1418 }, 1419 "node_modules/@rushstack/node-core-library/node_modules/ajv": { 1420 "version": "8.13.0", 1421 "dev": true, 1422 "license": "MIT", 1423 "dependencies": { ··· 1431 "url": "https://github.com/sponsors/epoberezkin" 1432 } 1433 }, 1434 + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { 1435 + "version": "11.3.2", 1436 "dev": true, 1437 "license": "MIT", 1438 "dependencies": { 1439 + "graceful-fs": "^4.2.0", 1440 + "jsonfile": "^6.0.1", 1441 + "universalify": "^2.0.0" 1442 }, 1443 "engines": { 1444 + "node": ">=14.14" 1445 } 1446 }, 1447 "node_modules/@rushstack/problem-matcher": { 1448 "version": "0.1.1", 1449 "dev": true, 1450 "license": "MIT", 1451 "peerDependencies": { ··· 1459 }, 1460 "node_modules/@rushstack/rig-package": { 1461 "version": "0.6.0", 1462 "dev": true, 1463 "license": "MIT", 1464 "dependencies": { ··· 1467 } 1468 }, 1469 "node_modules/@rushstack/terminal": { 1470 + "version": "0.19.4", 1471 "dev": true, 1472 "license": "MIT", 1473 "dependencies": { 1474 + "@rushstack/node-core-library": "5.19.0", 1475 "@rushstack/problem-matcher": "0.1.1", 1476 "supports-color": "~8.1.1" 1477 }, ··· 1484 } 1485 } 1486 }, 1487 "node_modules/@rushstack/ts-command-line": { 1488 + "version": "5.1.4", 1489 "dev": true, 1490 "license": "MIT", 1491 "dependencies": { 1492 + "@rushstack/terminal": "0.19.4", 1493 "@types/argparse": "1.0.38", 1494 "argparse": "~1.0.9", 1495 "string-argv": "~0.3.1" 1496 } 1497 }, 1498 + "node_modules/@standard-schema/spec": { 1499 + "version": "1.0.0", 1500 + "license": "MIT" 1501 + }, 1502 + "node_modules/@tybys/wasm-util": { 1503 + "version": "0.10.1", 1504 + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", 1505 + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", 1506 "dev": true, 1507 "license": "MIT", 1508 + "optional": true, 1509 "dependencies": { 1510 + "tslib": "^2.4.0" 1511 } 1512 }, 1513 "node_modules/@types/argparse": { 1514 "version": "1.0.38", 1515 "dev": true, 1516 "license": "MIT" 1517 }, 1518 "node_modules/@types/babel__core": { 1519 "version": "7.20.5", 1520 "dev": true, 1521 "license": "MIT", 1522 "dependencies": { ··· 1529 }, 1530 "node_modules/@types/babel__generator": { 1531 "version": "7.27.0", 1532 "dev": true, 1533 "license": "MIT", 1534 "dependencies": { ··· 1537 }, 1538 "node_modules/@types/babel__template": { 1539 "version": "7.4.4", 1540 "dev": true, 1541 "license": "MIT", 1542 "dependencies": { ··· 1546 }, 1547 "node_modules/@types/babel__traverse": { 1548 "version": "7.28.0", 1549 "dev": true, 1550 "license": "MIT", 1551 "dependencies": { ··· 1554 }, 1555 "node_modules/@types/estree": { 1556 "version": "1.0.8", 1557 "dev": true, 1558 "license": "MIT" 1559 }, 1560 "node_modules/@types/json-schema": { 1561 "version": "7.0.15", 1562 "dev": true, 1563 "license": "MIT" 1564 }, 1565 "node_modules/@types/node": { 1566 + "version": "24.10.1", 1567 "dev": true, 1568 "license": "MIT", 1569 + "peer": true, 1570 "dependencies": { 1571 + "undici-types": "~7.16.0" 1572 } 1573 }, 1574 "node_modules/@types/react": { 1575 + "version": "19.2.7", 1576 "dev": true, 1577 "license": "MIT", 1578 + "peer": true, 1579 "dependencies": { 1580 + "csstype": "^3.2.2" 1581 } 1582 }, 1583 "node_modules/@types/react-dom": { 1584 + "version": "19.2.3", 1585 "dev": true, 1586 "license": "MIT", 1587 "peerDependencies": { ··· 1589 } 1590 }, 1591 "node_modules/@typescript-eslint/eslint-plugin": { 1592 + "version": "8.48.1", 1593 "dev": true, 1594 "license": "MIT", 1595 "dependencies": { 1596 "@eslint-community/regexpp": "^4.10.0", 1597 + "@typescript-eslint/scope-manager": "8.48.1", 1598 + "@typescript-eslint/type-utils": "8.48.1", 1599 + "@typescript-eslint/utils": "8.48.1", 1600 + "@typescript-eslint/visitor-keys": "8.48.1", 1601 "graphemer": "^1.4.0", 1602 "ignore": "^7.0.0", 1603 "natural-compare": "^1.4.0", ··· 1611 "url": "https://opencollective.com/typescript-eslint" 1612 }, 1613 "peerDependencies": { 1614 + "@typescript-eslint/parser": "^8.48.1", 1615 "eslint": "^8.57.0 || ^9.0.0", 1616 "typescript": ">=4.8.4 <6.0.0" 1617 } 1618 }, 1619 "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1620 "version": "7.0.5", 1621 "dev": true, 1622 "license": "MIT", 1623 "engines": { ··· 1625 } 1626 }, 1627 "node_modules/@typescript-eslint/parser": { 1628 + "version": "8.48.1", 1629 "dev": true, 1630 "license": "MIT", 1631 + "peer": true, 1632 "dependencies": { 1633 + "@typescript-eslint/scope-manager": "8.48.1", 1634 + "@typescript-eslint/types": "8.48.1", 1635 + "@typescript-eslint/typescript-estree": "8.48.1", 1636 + "@typescript-eslint/visitor-keys": "8.48.1", 1637 "debug": "^4.3.4" 1638 }, 1639 "engines": { ··· 1649 } 1650 }, 1651 "node_modules/@typescript-eslint/project-service": { 1652 + "version": "8.48.1", 1653 "dev": true, 1654 "license": "MIT", 1655 "dependencies": { 1656 + "@typescript-eslint/tsconfig-utils": "^8.48.1", 1657 + "@typescript-eslint/types": "^8.48.1", 1658 "debug": "^4.3.4" 1659 }, 1660 "engines": { ··· 1669 } 1670 }, 1671 "node_modules/@typescript-eslint/scope-manager": { 1672 + "version": "8.48.1", 1673 "dev": true, 1674 "license": "MIT", 1675 "dependencies": { 1676 + "@typescript-eslint/types": "8.48.1", 1677 + "@typescript-eslint/visitor-keys": "8.48.1" 1678 }, 1679 "engines": { 1680 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1685 } 1686 }, 1687 "node_modules/@typescript-eslint/tsconfig-utils": { 1688 + "version": "8.48.1", 1689 "dev": true, 1690 "license": "MIT", 1691 "engines": { ··· 1700 } 1701 }, 1702 "node_modules/@typescript-eslint/type-utils": { 1703 + "version": "8.48.1", 1704 "dev": true, 1705 "license": "MIT", 1706 "dependencies": { 1707 + "@typescript-eslint/types": "8.48.1", 1708 + "@typescript-eslint/typescript-estree": "8.48.1", 1709 + "@typescript-eslint/utils": "8.48.1", 1710 "debug": "^4.3.4", 1711 "ts-api-utils": "^2.1.0" 1712 }, ··· 1723 } 1724 }, 1725 "node_modules/@typescript-eslint/types": { 1726 + "version": "8.48.1", 1727 "dev": true, 1728 "license": "MIT", 1729 "engines": { ··· 1735 } 1736 }, 1737 "node_modules/@typescript-eslint/typescript-estree": { 1738 + "version": "8.48.1", 1739 "dev": true, 1740 "license": "MIT", 1741 "dependencies": { 1742 + "@typescript-eslint/project-service": "8.48.1", 1743 + "@typescript-eslint/tsconfig-utils": "8.48.1", 1744 + "@typescript-eslint/types": "8.48.1", 1745 + "@typescript-eslint/visitor-keys": "8.48.1", 1746 "debug": "^4.3.4", 1747 "minimatch": "^9.0.4", 1748 "semver": "^7.6.0", 1749 + "tinyglobby": "^0.2.15", 1750 "ts-api-utils": "^2.1.0" 1751 }, 1752 "engines": { ··· 1760 "typescript": ">=4.8.4 <6.0.0" 1761 } 1762 }, 1763 "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1764 "version": "9.0.5", 1765 "dev": true, 1766 "license": "ISC", 1767 "dependencies": { ··· 1774 "url": "https://github.com/sponsors/isaacs" 1775 } 1776 }, 1777 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { 1778 + "version": "2.0.2", 1779 + "dev": true, 1780 + "license": "MIT", 1781 + "dependencies": { 1782 + "balanced-match": "^1.0.0" 1783 + } 1784 + }, 1785 "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1786 "version": "7.7.3", 1787 "dev": true, 1788 "license": "ISC", 1789 "bin": { ··· 1794 } 1795 }, 1796 "node_modules/@typescript-eslint/utils": { 1797 + "version": "8.48.1", 1798 "dev": true, 1799 "license": "MIT", 1800 "dependencies": { 1801 "@eslint-community/eslint-utils": "^4.7.0", 1802 + "@typescript-eslint/scope-manager": "8.48.1", 1803 + "@typescript-eslint/types": "8.48.1", 1804 + "@typescript-eslint/typescript-estree": "8.48.1" 1805 }, 1806 "engines": { 1807 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1816 } 1817 }, 1818 "node_modules/@typescript-eslint/visitor-keys": { 1819 + "version": "8.48.1", 1820 "dev": true, 1821 "license": "MIT", 1822 "dependencies": { 1823 + "@typescript-eslint/types": "8.48.1", 1824 "eslint-visitor-keys": "^4.2.1" 1825 }, 1826 "engines": { ··· 1832 } 1833 }, 1834 "node_modules/@vitejs/plugin-react": { 1835 + "version": "5.1.1", 1836 "dev": true, 1837 "license": "MIT", 1838 "dependencies": { 1839 + "@babel/core": "^7.28.5", 1840 "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1841 "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1842 + "@rolldown/pluginutils": "1.0.0-beta.47", 1843 "@types/babel__core": "^7.20.5", 1844 + "react-refresh": "^0.18.0" 1845 }, 1846 "engines": { 1847 "node": "^20.19.0 || >=22.12.0" ··· 1851 } 1852 }, 1853 "node_modules/@volar/language-core": { 1854 + "version": "2.4.26", 1855 "dev": true, 1856 "license": "MIT", 1857 "dependencies": { 1858 + "@volar/source-map": "2.4.26" 1859 } 1860 }, 1861 "node_modules/@volar/source-map": { 1862 + "version": "2.4.26", 1863 "dev": true, 1864 "license": "MIT" 1865 }, 1866 "node_modules/@volar/typescript": { 1867 + "version": "2.4.26", 1868 "dev": true, 1869 "license": "MIT", 1870 "dependencies": { 1871 + "@volar/language-core": "2.4.26", 1872 "path-browserify": "^1.0.1", 1873 "vscode-uri": "^3.0.8" 1874 } 1875 }, 1876 "node_modules/acorn": { 1877 "version": "8.15.0", 1878 "dev": true, 1879 "license": "MIT", 1880 + "peer": true, 1881 "bin": { 1882 "acorn": "bin/acorn" 1883 }, ··· 1887 }, 1888 "node_modules/acorn-jsx": { 1889 "version": "5.3.2", 1890 "dev": true, 1891 "license": "MIT", 1892 "peerDependencies": { ··· 1894 } 1895 }, 1896 "node_modules/ajv": { 1897 + "version": "8.17.1", 1898 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 1899 + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 1900 "dev": true, 1901 "license": "MIT", 1902 + "peer": true, 1903 "dependencies": { 1904 + "fast-deep-equal": "^3.1.3", 1905 + "fast-uri": "^3.0.1", 1906 + "json-schema-traverse": "^1.0.0", 1907 + "require-from-string": "^2.0.2" 1908 }, 1909 "funding": { 1910 "type": "github", 1911 "url": "https://github.com/sponsors/epoberezkin" 1912 } 1913 }, 1914 + "node_modules/ajv-draft-04": { 1915 + "version": "1.0.0", 1916 + "dev": true, 1917 + "license": "MIT", 1918 + "peerDependencies": { 1919 + "ajv": "^8.5.0" 1920 + }, 1921 + "peerDependenciesMeta": { 1922 + "ajv": { 1923 + "optional": true 1924 + } 1925 + } 1926 + }, 1927 "node_modules/ajv-formats": { 1928 "version": "3.0.1", 1929 "dev": true, 1930 "license": "MIT", 1931 "dependencies": { ··· 1940 } 1941 } 1942 }, 1943 "node_modules/ansi-styles": { 1944 "version": "4.3.0", 1945 "dev": true, 1946 "license": "MIT", 1947 "dependencies": { ··· 1956 }, 1957 "node_modules/ansis": { 1958 "version": "4.2.0", 1959 "dev": true, 1960 "license": "ISC", 1961 "engines": { ··· 1963 } 1964 }, 1965 "node_modules/argparse": { 1966 + "version": "1.0.10", 1967 "dev": true, 1968 + "license": "MIT", 1969 + "dependencies": { 1970 + "sprintf-js": "~1.0.2" 1971 + } 1972 }, 1973 "node_modules/balanced-match": { 1974 "version": "1.0.2", 1975 "dev": true, 1976 "license": "MIT" 1977 }, 1978 "node_modules/baseline-browser-mapping": { 1979 + "version": "2.8.32", 1980 "dev": true, 1981 "license": "Apache-2.0", 1982 "bin": { ··· 1985 }, 1986 "node_modules/brace-expansion": { 1987 "version": "1.1.12", 1988 "dev": true, 1989 "license": "MIT", 1990 "dependencies": { ··· 1992 "concat-map": "0.0.1" 1993 } 1994 }, 1995 "node_modules/browserslist": { 1996 + "version": "4.28.0", 1997 "dev": true, 1998 "funding": [ 1999 { ··· 2010 } 2011 ], 2012 "license": "MIT", 2013 + "peer": true, 2014 "dependencies": { 2015 + "baseline-browser-mapping": "^2.8.25", 2016 + "caniuse-lite": "^1.0.30001754", 2017 + "electron-to-chromium": "^1.5.249", 2018 + "node-releases": "^2.0.27", 2019 + "update-browserslist-db": "^1.1.4" 2020 }, 2021 "bin": { 2022 "browserslist": "cli.js" ··· 2027 }, 2028 "node_modules/buffer-from": { 2029 "version": "1.1.2", 2030 "dev": true, 2031 "license": "MIT", 2032 + "optional": true 2033 }, 2034 "node_modules/callsites": { 2035 "version": "3.1.0", 2036 "dev": true, 2037 "license": "MIT", 2038 "engines": { ··· 2040 } 2041 }, 2042 "node_modules/caniuse-lite": { 2043 + "version": "1.0.30001759", 2044 "dev": true, 2045 "funding": [ 2046 { ··· 2060 }, 2061 "node_modules/chalk": { 2062 "version": "4.1.2", 2063 "dev": true, 2064 "license": "MIT", 2065 "dependencies": { ··· 2073 "url": "https://github.com/chalk/chalk?sponsor=1" 2074 } 2075 }, 2076 + "node_modules/chalk/node_modules/supports-color": { 2077 + "version": "7.2.0", 2078 + "dev": true, 2079 + "license": "MIT", 2080 + "dependencies": { 2081 + "has-flag": "^4.0.0" 2082 + }, 2083 + "engines": { 2084 + "node": ">=8" 2085 + } 2086 + }, 2087 "node_modules/color-convert": { 2088 "version": "2.0.1", 2089 "dev": true, 2090 "license": "MIT", 2091 "dependencies": { ··· 2097 }, 2098 "node_modules/color-name": { 2099 "version": "1.1.4", 2100 "dev": true, 2101 "license": "MIT" 2102 }, 2103 "node_modules/commander": { 2104 "version": "2.20.3", 2105 "dev": true, 2106 "license": "MIT", 2107 + "optional": true 2108 }, 2109 "node_modules/commondir": { 2110 "version": "1.0.1", 2111 "dev": true, 2112 "license": "MIT" 2113 }, 2114 "node_modules/compare-versions": { 2115 "version": "6.1.1", 2116 "dev": true, 2117 "license": "MIT" 2118 }, 2119 "node_modules/concat-map": { 2120 "version": "0.0.1", 2121 "dev": true, 2122 "license": "MIT" 2123 }, 2124 "node_modules/confbox": { 2125 "version": "0.2.2", 2126 "dev": true, 2127 "license": "MIT" 2128 }, 2129 "node_modules/convert-source-map": { 2130 "version": "2.0.0", 2131 "dev": true, 2132 "license": "MIT" 2133 }, 2134 "node_modules/cross-spawn": { 2135 "version": "7.0.6", 2136 "dev": true, 2137 "license": "MIT", 2138 "dependencies": { ··· 2145 } 2146 }, 2147 "node_modules/csstype": { 2148 + "version": "3.2.3", 2149 "dev": true, 2150 "license": "MIT" 2151 }, 2152 "node_modules/debug": { 2153 "version": "4.4.3", 2154 "dev": true, 2155 "license": "MIT", 2156 "dependencies": { ··· 2167 }, 2168 "node_modules/deep-is": { 2169 "version": "0.1.4", 2170 "dev": true, 2171 "license": "MIT" 2172 }, 2173 "node_modules/detect-libc": { 2174 "version": "2.1.2", 2175 "dev": true, 2176 "license": "Apache-2.0", 2177 "engines": { 2178 "node": ">=8" 2179 } 2180 }, 2181 + "node_modules/diff": { 2182 + "version": "8.0.2", 2183 + "dev": true, 2184 + "license": "BSD-3-Clause", 2185 + "engines": { 2186 + "node": ">=0.3.1" 2187 + } 2188 + }, 2189 "node_modules/electron-to-chromium": { 2190 + "version": "1.5.263", 2191 "dev": true, 2192 "license": "ISC" 2193 }, 2194 "node_modules/escalade": { 2195 "version": "3.2.0", 2196 "dev": true, 2197 "license": "MIT", 2198 "engines": { ··· 2201 }, 2202 "node_modules/escape-string-regexp": { 2203 "version": "4.0.0", 2204 "dev": true, 2205 "license": "MIT", 2206 "engines": { ··· 2211 } 2212 }, 2213 "node_modules/eslint": { 2214 + "version": "9.39.1", 2215 "dev": true, 2216 "license": "MIT", 2217 + "peer": true, 2218 "dependencies": { 2219 "@eslint-community/eslint-utils": "^4.8.0", 2220 "@eslint-community/regexpp": "^4.12.1", 2221 + "@eslint/config-array": "^0.21.1", 2222 + "@eslint/config-helpers": "^0.4.2", 2223 + "@eslint/core": "^0.17.0", 2224 "@eslint/eslintrc": "^3.3.1", 2225 + "@eslint/js": "9.39.1", 2226 + "@eslint/plugin-kit": "^0.4.1", 2227 "@humanfs/node": "^0.16.6", 2228 "@humanwhocodes/module-importer": "^1.0.1", 2229 "@humanwhocodes/retry": "^0.4.2", 2230 "@types/estree": "^1.0.6", 2231 "ajv": "^6.12.4", 2232 "chalk": "^4.0.0", 2233 "cross-spawn": "^7.0.6", ··· 2271 }, 2272 "node_modules/eslint-plugin-react-hooks": { 2273 "version": "5.2.0", 2274 "dev": true, 2275 "license": "MIT", 2276 "engines": { ··· 2281 } 2282 }, 2283 "node_modules/eslint-plugin-react-refresh": { 2284 + "version": "0.4.24", 2285 "dev": true, 2286 "license": "MIT", 2287 "peerDependencies": { ··· 2290 }, 2291 "node_modules/eslint-scope": { 2292 "version": "8.4.0", 2293 "dev": true, 2294 "license": "BSD-2-Clause", 2295 "dependencies": { ··· 2305 }, 2306 "node_modules/eslint-visitor-keys": { 2307 "version": "4.2.1", 2308 "dev": true, 2309 "license": "Apache-2.0", 2310 "engines": { ··· 2314 "url": "https://opencollective.com/eslint" 2315 } 2316 }, 2317 + "node_modules/eslint/node_modules/ajv": { 2318 + "version": "6.12.6", 2319 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2320 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2321 + "dev": true, 2322 + "license": "MIT", 2323 + "dependencies": { 2324 + "fast-deep-equal": "^3.1.1", 2325 + "fast-json-stable-stringify": "^2.0.0", 2326 + "json-schema-traverse": "^0.4.1", 2327 + "uri-js": "^4.2.2" 2328 + }, 2329 + "funding": { 2330 + "type": "github", 2331 + "url": "https://github.com/sponsors/epoberezkin" 2332 + } 2333 + }, 2334 + "node_modules/eslint/node_modules/json-schema-traverse": { 2335 + "version": "0.4.1", 2336 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 2337 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 2338 + "dev": true, 2339 + "license": "MIT" 2340 + }, 2341 + "node_modules/eslint/node_modules/minimatch": { 2342 + "version": "3.1.2", 2343 + "dev": true, 2344 + "license": "ISC", 2345 + "dependencies": { 2346 + "brace-expansion": "^1.1.7" 2347 + }, 2348 + "engines": { 2349 + "node": "*" 2350 + } 2351 + }, 2352 "node_modules/esm-env": { 2353 "version": "1.2.2", 2354 "license": "MIT" 2355 }, 2356 "node_modules/espree": { 2357 "version": "10.4.0", 2358 "dev": true, 2359 "license": "BSD-2-Clause", 2360 "dependencies": { ··· 2371 }, 2372 "node_modules/esquery": { 2373 "version": "1.6.0", 2374 "dev": true, 2375 "license": "BSD-3-Clause", 2376 "dependencies": { ··· 2382 }, 2383 "node_modules/esrecurse": { 2384 "version": "4.3.0", 2385 "dev": true, 2386 "license": "BSD-2-Clause", 2387 "dependencies": { ··· 2393 }, 2394 "node_modules/estraverse": { 2395 "version": "5.3.0", 2396 "dev": true, 2397 "license": "BSD-2-Clause", 2398 "engines": { ··· 2401 }, 2402 "node_modules/estree-walker": { 2403 "version": "2.0.2", 2404 "dev": true, 2405 "license": "MIT" 2406 }, 2407 "node_modules/esutils": { 2408 "version": "2.0.3", 2409 "dev": true, 2410 "license": "BSD-2-Clause", 2411 "engines": { ··· 2413 } 2414 }, 2415 "node_modules/exsolve": { 2416 + "version": "1.0.8", 2417 "dev": true, 2418 "license": "MIT" 2419 }, 2420 "node_modules/fast-deep-equal": { 2421 "version": "3.1.3", 2422 "dev": true, 2423 "license": "MIT" 2424 }, 2425 "node_modules/fast-json-stable-stringify": { 2426 "version": "2.1.0", 2427 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", ··· 2431 }, 2432 "node_modules/fast-levenshtein": { 2433 "version": "2.0.6", 2434 "dev": true, 2435 "license": "MIT" 2436 }, 2437 "node_modules/fast-uri": { 2438 "version": "3.1.0", 2439 "dev": true, 2440 "funding": [ 2441 { ··· 2449 ], 2450 "license": "BSD-3-Clause" 2451 }, 2452 + "node_modules/fdir": { 2453 + "version": "6.5.0", 2454 "dev": true, 2455 + "license": "MIT", 2456 + "engines": { 2457 + "node": ">=12.0.0" 2458 + }, 2459 + "peerDependencies": { 2460 + "picomatch": "^3 || ^4" 2461 + }, 2462 + "peerDependenciesMeta": { 2463 + "picomatch": { 2464 + "optional": true 2465 + } 2466 } 2467 }, 2468 "node_modules/file-entry-cache": { 2469 "version": "8.0.0", 2470 "dev": true, 2471 "license": "MIT", 2472 "dependencies": { ··· 2476 "node": ">=16.0.0" 2477 } 2478 }, 2479 "node_modules/find-cache-dir": { 2480 "version": "3.3.2", 2481 "dev": true, 2482 "license": "MIT", 2483 "dependencies": { ··· 2494 }, 2495 "node_modules/find-up": { 2496 "version": "5.0.0", 2497 "dev": true, 2498 "license": "MIT", 2499 "dependencies": { ··· 2509 }, 2510 "node_modules/flat-cache": { 2511 "version": "4.0.1", 2512 "dev": true, 2513 "license": "MIT", 2514 "dependencies": { ··· 2521 }, 2522 "node_modules/flatted": { 2523 "version": "3.3.3", 2524 "dev": true, 2525 "license": "ISC" 2526 }, 2527 "node_modules/fs-extra": { 2528 + "version": "10.1.0", 2529 "dev": true, 2530 "license": "MIT", 2531 "dependencies": { ··· 2534 "universalify": "^2.0.0" 2535 }, 2536 "engines": { 2537 + "node": ">=12" 2538 } 2539 }, 2540 "node_modules/fsevents": { ··· 2554 }, 2555 "node_modules/function-bind": { 2556 "version": "1.1.2", 2557 "dev": true, 2558 "license": "MIT", 2559 "funding": { ··· 2562 }, 2563 "node_modules/gensync": { 2564 "version": "1.0.0-beta.2", 2565 "dev": true, 2566 "license": "MIT", 2567 "engines": { ··· 2570 }, 2571 "node_modules/glob-parent": { 2572 "version": "6.0.2", 2573 "dev": true, 2574 "license": "ISC", 2575 "dependencies": { ··· 2580 } 2581 }, 2582 "node_modules/globals": { 2583 + "version": "16.5.0", 2584 "dev": true, 2585 "license": "MIT", 2586 "engines": { ··· 2592 }, 2593 "node_modules/graceful-fs": { 2594 "version": "4.2.11", 2595 "dev": true, 2596 "license": "ISC" 2597 }, 2598 "node_modules/graphemer": { 2599 "version": "1.4.0", 2600 "dev": true, 2601 "license": "MIT" 2602 }, 2603 "node_modules/has-flag": { 2604 "version": "4.0.0", 2605 "dev": true, 2606 "license": "MIT", 2607 "engines": { ··· 2610 }, 2611 "node_modules/hasown": { 2612 "version": "2.0.2", 2613 "dev": true, 2614 "license": "MIT", 2615 "dependencies": { ··· 2621 }, 2622 "node_modules/ignore": { 2623 "version": "5.3.2", 2624 "dev": true, 2625 "license": "MIT", 2626 "engines": { ··· 2629 }, 2630 "node_modules/import-fresh": { 2631 "version": "3.3.1", 2632 "dev": true, 2633 "license": "MIT", 2634 "dependencies": { ··· 2644 }, 2645 "node_modules/import-lazy": { 2646 "version": "4.0.0", 2647 "dev": true, 2648 "license": "MIT", 2649 "engines": { ··· 2652 }, 2653 "node_modules/imurmurhash": { 2654 "version": "0.1.4", 2655 "dev": true, 2656 "license": "MIT", 2657 "engines": { ··· 2660 }, 2661 "node_modules/is-core-module": { 2662 "version": "2.16.1", 2663 "dev": true, 2664 "license": "MIT", 2665 "dependencies": { ··· 2674 }, 2675 "node_modules/is-extglob": { 2676 "version": "2.1.1", 2677 "dev": true, 2678 "license": "MIT", 2679 "engines": { ··· 2682 }, 2683 "node_modules/is-glob": { 2684 "version": "4.0.3", 2685 "dev": true, 2686 "license": "MIT", 2687 "dependencies": { ··· 2691 "node": ">=0.10.0" 2692 } 2693 }, 2694 "node_modules/isexe": { 2695 "version": "2.0.0", 2696 "dev": true, 2697 "license": "ISC" 2698 }, 2699 "node_modules/jju": { 2700 "version": "1.4.0", 2701 "dev": true, 2702 "license": "MIT" 2703 }, 2704 "node_modules/js-tokens": { 2705 "version": "4.0.0", 2706 "dev": true, 2707 "license": "MIT" 2708 }, 2709 "node_modules/js-yaml": { 2710 + "version": "4.1.1", 2711 "dev": true, 2712 "license": "MIT", 2713 "dependencies": { ··· 2717 "js-yaml": "bin/js-yaml.js" 2718 } 2719 }, 2720 + "node_modules/js-yaml/node_modules/argparse": { 2721 + "version": "2.0.1", 2722 + "dev": true, 2723 + "license": "Python-2.0" 2724 + }, 2725 "node_modules/jsesc": { 2726 "version": "3.1.0", 2727 "dev": true, 2728 "license": "MIT", 2729 "bin": { ··· 2735 }, 2736 "node_modules/json-buffer": { 2737 "version": "3.0.1", 2738 "dev": true, 2739 "license": "MIT" 2740 }, 2741 "node_modules/json-schema-traverse": { 2742 + "version": "1.0.0", 2743 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 2744 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 2745 "dev": true, 2746 "license": "MIT" 2747 }, 2748 "node_modules/json-stable-stringify-without-jsonify": { 2749 "version": "1.0.1", 2750 "dev": true, 2751 "license": "MIT" 2752 }, 2753 "node_modules/json5": { 2754 "version": "2.2.3", 2755 "dev": true, 2756 "license": "MIT", 2757 "bin": { ··· 2763 }, 2764 "node_modules/jsonfile": { 2765 "version": "6.2.0", 2766 "dev": true, 2767 "license": "MIT", 2768 "dependencies": { ··· 2774 }, 2775 "node_modules/keyv": { 2776 "version": "4.5.4", 2777 "dev": true, 2778 "license": "MIT", 2779 "dependencies": { ··· 2782 }, 2783 "node_modules/kolorist": { 2784 "version": "1.8.0", 2785 "dev": true, 2786 "license": "MIT" 2787 }, 2788 "node_modules/levn": { 2789 "version": "0.4.1", 2790 "dev": true, 2791 "license": "MIT", 2792 "dependencies": { ··· 2799 }, 2800 "node_modules/lightningcss": { 2801 "version": "1.30.2", 2802 "dev": true, 2803 "license": "MPL-2.0", 2804 "dependencies": { ··· 2825 "lightningcss-win32-x64-msvc": "1.30.2" 2826 } 2827 }, 2828 + "node_modules/lightningcss-android-arm64": { 2829 + "version": "1.30.2", 2830 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", 2831 + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", 2832 + "cpu": [ 2833 + "arm64" 2834 + ], 2835 + "dev": true, 2836 + "license": "MPL-2.0", 2837 + "optional": true, 2838 + "os": [ 2839 + "android" 2840 + ], 2841 + "engines": { 2842 + "node": ">= 12.0.0" 2843 + }, 2844 + "funding": { 2845 + "type": "opencollective", 2846 + "url": "https://opencollective.com/parcel" 2847 + } 2848 + }, 2849 "node_modules/lightningcss-darwin-arm64": { 2850 "version": "1.30.2", 2851 "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", ··· 2867 "url": "https://opencollective.com/parcel" 2868 } 2869 }, 2870 + "node_modules/lightningcss-darwin-x64": { 2871 + "version": "1.30.2", 2872 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", 2873 + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", 2874 + "cpu": [ 2875 + "x64" 2876 + ], 2877 + "dev": true, 2878 + "license": "MPL-2.0", 2879 + "optional": true, 2880 + "os": [ 2881 + "darwin" 2882 + ], 2883 + "engines": { 2884 + "node": ">= 12.0.0" 2885 + }, 2886 + "funding": { 2887 + "type": "opencollective", 2888 + "url": "https://opencollective.com/parcel" 2889 + } 2890 + }, 2891 + "node_modules/lightningcss-freebsd-x64": { 2892 + "version": "1.30.2", 2893 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", 2894 + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", 2895 + "cpu": [ 2896 + "x64" 2897 + ], 2898 + "dev": true, 2899 + "license": "MPL-2.0", 2900 + "optional": true, 2901 + "os": [ 2902 + "freebsd" 2903 + ], 2904 + "engines": { 2905 + "node": ">= 12.0.0" 2906 + }, 2907 + "funding": { 2908 + "type": "opencollective", 2909 + "url": "https://opencollective.com/parcel" 2910 + } 2911 + }, 2912 + "node_modules/lightningcss-linux-arm-gnueabihf": { 2913 + "version": "1.30.2", 2914 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", 2915 + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", 2916 + "cpu": [ 2917 + "arm" 2918 + ], 2919 + "dev": true, 2920 + "license": "MPL-2.0", 2921 + "optional": true, 2922 + "os": [ 2923 + "linux" 2924 + ], 2925 + "engines": { 2926 + "node": ">= 12.0.0" 2927 + }, 2928 + "funding": { 2929 + "type": "opencollective", 2930 + "url": "https://opencollective.com/parcel" 2931 + } 2932 + }, 2933 + "node_modules/lightningcss-linux-arm64-gnu": { 2934 + "version": "1.30.2", 2935 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", 2936 + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", 2937 + "cpu": [ 2938 + "arm64" 2939 + ], 2940 + "dev": true, 2941 + "license": "MPL-2.0", 2942 + "optional": true, 2943 + "os": [ 2944 + "linux" 2945 + ], 2946 + "engines": { 2947 + "node": ">= 12.0.0" 2948 + }, 2949 + "funding": { 2950 + "type": "opencollective", 2951 + "url": "https://opencollective.com/parcel" 2952 + } 2953 + }, 2954 + "node_modules/lightningcss-linux-arm64-musl": { 2955 + "version": "1.30.2", 2956 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", 2957 + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", 2958 + "cpu": [ 2959 + "arm64" 2960 + ], 2961 + "dev": true, 2962 + "license": "MPL-2.0", 2963 + "optional": true, 2964 + "os": [ 2965 + "linux" 2966 + ], 2967 + "engines": { 2968 + "node": ">= 12.0.0" 2969 + }, 2970 + "funding": { 2971 + "type": "opencollective", 2972 + "url": "https://opencollective.com/parcel" 2973 + } 2974 + }, 2975 + "node_modules/lightningcss-linux-x64-gnu": { 2976 + "version": "1.30.2", 2977 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", 2978 + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", 2979 + "cpu": [ 2980 + "x64" 2981 + ], 2982 + "dev": true, 2983 + "license": "MPL-2.0", 2984 + "optional": true, 2985 + "os": [ 2986 + "linux" 2987 + ], 2988 + "engines": { 2989 + "node": ">= 12.0.0" 2990 + }, 2991 + "funding": { 2992 + "type": "opencollective", 2993 + "url": "https://opencollective.com/parcel" 2994 + } 2995 + }, 2996 + "node_modules/lightningcss-linux-x64-musl": { 2997 + "version": "1.30.2", 2998 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", 2999 + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", 3000 + "cpu": [ 3001 + "x64" 3002 + ], 3003 + "dev": true, 3004 + "license": "MPL-2.0", 3005 + "optional": true, 3006 + "os": [ 3007 + "linux" 3008 + ], 3009 + "engines": { 3010 + "node": ">= 12.0.0" 3011 + }, 3012 + "funding": { 3013 + "type": "opencollective", 3014 + "url": "https://opencollective.com/parcel" 3015 + } 3016 + }, 3017 + "node_modules/lightningcss-win32-arm64-msvc": { 3018 + "version": "1.30.2", 3019 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", 3020 + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", 3021 + "cpu": [ 3022 + "arm64" 3023 + ], 3024 + "dev": true, 3025 + "license": "MPL-2.0", 3026 + "optional": true, 3027 + "os": [ 3028 + "win32" 3029 + ], 3030 + "engines": { 3031 + "node": ">= 12.0.0" 3032 + }, 3033 + "funding": { 3034 + "type": "opencollective", 3035 + "url": "https://opencollective.com/parcel" 3036 + } 3037 + }, 3038 + "node_modules/lightningcss-win32-x64-msvc": { 3039 + "version": "1.30.2", 3040 + "cpu": [ 3041 + "x64" 3042 + ], 3043 + "dev": true, 3044 + "license": "MPL-2.0", 3045 + "optional": true, 3046 + "os": [ 3047 + "win32" 3048 + ], 3049 + "engines": { 3050 + "node": ">= 12.0.0" 3051 + }, 3052 + "funding": { 3053 + "type": "opencollective", 3054 + "url": "https://opencollective.com/parcel" 3055 + } 3056 + }, 3057 "node_modules/local-pkg": { 3058 "version": "1.1.2", 3059 "dev": true, 3060 "license": "MIT", 3061 "dependencies": { ··· 3072 }, 3073 "node_modules/locate-path": { 3074 "version": "6.0.0", 3075 "dev": true, 3076 "license": "MIT", 3077 "dependencies": { ··· 3086 }, 3087 "node_modules/lodash": { 3088 "version": "4.17.21", 3089 "dev": true, 3090 "license": "MIT" 3091 }, 3092 "node_modules/lodash.merge": { 3093 "version": "4.6.2", 3094 "dev": true, 3095 "license": "MIT" 3096 }, 3097 "node_modules/lru-cache": { 3098 + "version": "6.0.0", 3099 "dev": true, 3100 "license": "ISC", 3101 "dependencies": { 3102 + "yallist": "^4.0.0" 3103 + }, 3104 + "engines": { 3105 + "node": ">=10" 3106 } 3107 }, 3108 "node_modules/magic-string": { 3109 + "version": "0.30.21", 3110 "dev": true, 3111 "license": "MIT", 3112 "dependencies": { ··· 3115 }, 3116 "node_modules/make-dir": { 3117 "version": "3.1.0", 3118 "dev": true, 3119 "license": "MIT", 3120 "dependencies": { ··· 3127 "url": "https://github.com/sponsors/sindresorhus" 3128 } 3129 }, 3130 + "node_modules/make-dir/node_modules/semver": { 3131 + "version": "6.3.1", 3132 "dev": true, 3133 + "license": "ISC", 3134 + "bin": { 3135 + "semver": "bin/semver.js" 3136 } 3137 }, 3138 "node_modules/minimatch": { 3139 + "version": "10.0.3", 3140 "dev": true, 3141 "license": "ISC", 3142 "dependencies": { 3143 + "@isaacs/brace-expansion": "^5.0.0" 3144 }, 3145 "engines": { 3146 + "node": "20 || >=22" 3147 + }, 3148 + "funding": { 3149 + "url": "https://github.com/sponsors/isaacs" 3150 } 3151 }, 3152 "node_modules/mlly": { 3153 "version": "1.8.0", 3154 "dev": true, 3155 "license": "MIT", 3156 "dependencies": { ··· 3160 "ufo": "^1.6.1" 3161 } 3162 }, 3163 "node_modules/mlly/node_modules/pkg-types": { 3164 "version": "1.3.1", 3165 "dev": true, 3166 "license": "MIT", 3167 "dependencies": { ··· 3169 "mlly": "^1.7.4", 3170 "pathe": "^2.0.1" 3171 } 3172 + }, 3173 + "node_modules/mlly/node_modules/pkg-types/node_modules/confbox": { 3174 + "version": "0.1.8", 3175 + "dev": true, 3176 + "license": "MIT" 3177 }, 3178 "node_modules/ms": { 3179 "version": "2.1.3", 3180 "dev": true, 3181 "license": "MIT" 3182 }, 3183 "node_modules/nanoid": { 3184 "version": "3.3.11", 3185 "dev": true, 3186 "funding": [ 3187 { ··· 3199 }, 3200 "node_modules/natural-compare": { 3201 "version": "1.4.0", 3202 "dev": true, 3203 "license": "MIT" 3204 }, 3205 "node_modules/node-releases": { 3206 + "version": "2.0.27", 3207 "dev": true, 3208 "license": "MIT" 3209 }, 3210 "node_modules/optionator": { 3211 "version": "0.9.4", 3212 "dev": true, 3213 "license": "MIT", 3214 "dependencies": { ··· 3225 }, 3226 "node_modules/p-limit": { 3227 "version": "3.1.0", 3228 "dev": true, 3229 "license": "MIT", 3230 "dependencies": { ··· 3239 }, 3240 "node_modules/p-locate": { 3241 "version": "5.0.0", 3242 "dev": true, 3243 "license": "MIT", 3244 "dependencies": { ··· 3253 }, 3254 "node_modules/p-try": { 3255 "version": "2.2.0", 3256 "dev": true, 3257 "license": "MIT", 3258 "engines": { ··· 3261 }, 3262 "node_modules/parent-module": { 3263 "version": "1.0.1", 3264 "dev": true, 3265 "license": "MIT", 3266 "dependencies": { ··· 3272 }, 3273 "node_modules/path-browserify": { 3274 "version": "1.0.1", 3275 "dev": true, 3276 "license": "MIT" 3277 }, 3278 "node_modules/path-exists": { 3279 "version": "4.0.0", 3280 "dev": true, 3281 "license": "MIT", 3282 "engines": { ··· 3285 }, 3286 "node_modules/path-key": { 3287 "version": "3.1.1", 3288 "dev": true, 3289 "license": "MIT", 3290 "engines": { ··· 3293 }, 3294 "node_modules/path-parse": { 3295 "version": "1.0.7", 3296 "dev": true, 3297 "license": "MIT" 3298 }, 3299 "node_modules/pathe": { 3300 "version": "2.0.3", 3301 "dev": true, 3302 "license": "MIT" 3303 }, 3304 "node_modules/picocolors": { 3305 "version": "1.1.1", 3306 "dev": true, 3307 "license": "ISC" 3308 }, 3309 "node_modules/picomatch": { 3310 + "version": "4.0.3", 3311 "dev": true, 3312 "license": "MIT", 3313 "engines": { 3314 + "node": ">=12" 3315 }, 3316 "funding": { 3317 "url": "https://github.com/sponsors/jonschlinkert" ··· 3319 }, 3320 "node_modules/pkg-dir": { 3321 "version": "4.2.0", 3322 "dev": true, 3323 "license": "MIT", 3324 "dependencies": { ··· 3330 }, 3331 "node_modules/pkg-dir/node_modules/find-up": { 3332 "version": "4.1.0", 3333 "dev": true, 3334 "license": "MIT", 3335 "dependencies": { ··· 3340 "node": ">=8" 3341 } 3342 }, 3343 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path": { 3344 "version": "5.0.0", 3345 "dev": true, 3346 "license": "MIT", 3347 "dependencies": { ··· 3351 "node": ">=8" 3352 } 3353 }, 3354 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { 3355 + "version": "4.1.0", 3356 "dev": true, 3357 "license": "MIT", 3358 "dependencies": { 3359 + "p-limit": "^2.2.0" 3360 }, 3361 "engines": { 3362 + "node": ">=8" 3363 } 3364 }, 3365 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { 3366 + "version": "2.3.0", 3367 "dev": true, 3368 "license": "MIT", 3369 "dependencies": { 3370 + "p-try": "^2.0.0" 3371 }, 3372 "engines": { 3373 + "node": ">=6" 3374 + }, 3375 + "funding": { 3376 + "url": "https://github.com/sponsors/sindresorhus" 3377 } 3378 }, 3379 "node_modules/pkg-types": { 3380 "version": "2.3.0", 3381 "dev": true, 3382 "license": "MIT", 3383 "dependencies": { ··· 3388 }, 3389 "node_modules/postcss": { 3390 "version": "8.5.6", 3391 "dev": true, 3392 "funding": [ 3393 { ··· 3415 }, 3416 "node_modules/prelude-ls": { 3417 "version": "1.2.1", 3418 "dev": true, 3419 "license": "MIT", 3420 "engines": { ··· 3423 }, 3424 "node_modules/punycode": { 3425 "version": "2.3.1", 3426 "dev": true, 3427 "license": "MIT", 3428 "engines": { ··· 3431 }, 3432 "node_modules/quansync": { 3433 "version": "0.2.11", 3434 "dev": true, 3435 "funding": [ 3436 { ··· 3444 ], 3445 "license": "MIT" 3446 }, 3447 "node_modules/react": { 3448 "version": "19.2.0", 3449 "dev": true, 3450 "license": "MIT", 3451 + "peer": true, 3452 "engines": { 3453 "node": ">=0.10.0" 3454 } 3455 }, 3456 "node_modules/react-dom": { 3457 "version": "19.2.0", 3458 "dev": true, 3459 "license": "MIT", 3460 "dependencies": { ··· 3465 } 3466 }, 3467 "node_modules/react-refresh": { 3468 + "version": "0.18.0", 3469 "dev": true, 3470 "license": "MIT", 3471 "engines": { ··· 3474 }, 3475 "node_modules/require-from-string": { 3476 "version": "2.0.2", 3477 "dev": true, 3478 "license": "MIT", 3479 "engines": { ··· 3481 } 3482 }, 3483 "node_modules/resolve": { 3484 + "version": "1.22.11", 3485 "dev": true, 3486 "license": "MIT", 3487 "dependencies": { 3488 + "is-core-module": "^2.16.1", 3489 "path-parse": "^1.0.7", 3490 "supports-preserve-symlinks-flag": "^1.0.0" 3491 }, ··· 3501 }, 3502 "node_modules/resolve-from": { 3503 "version": "4.0.0", 3504 "dev": true, 3505 "license": "MIT", 3506 "engines": { 3507 "node": ">=4" 3508 } 3509 }, 3510 "node_modules/rolldown": { 3511 "version": "1.0.0-beta.41", 3512 "dev": true, 3513 "license": "MIT", 3514 + "peer": true, 3515 "dependencies": { 3516 "@oxc-project/types": "=0.93.0", 3517 "@rolldown/pluginutils": "1.0.0-beta.41", ··· 3542 }, 3543 "node_modules/rolldown/node_modules/@rolldown/pluginutils": { 3544 "version": "1.0.0-beta.41", 3545 "dev": true, 3546 "license": "MIT" 3547 }, 3548 "node_modules/rollup": { 3549 + "version": "4.53.3", 3550 "dev": true, 3551 "license": "MIT", 3552 "peer": true, ··· 3561 "npm": ">=8.0.0" 3562 }, 3563 "optionalDependencies": { 3564 + "@rollup/rollup-android-arm-eabi": "4.53.3", 3565 + "@rollup/rollup-android-arm64": "4.53.3", 3566 + "@rollup/rollup-darwin-arm64": "4.53.3", 3567 + "@rollup/rollup-darwin-x64": "4.53.3", 3568 + "@rollup/rollup-freebsd-arm64": "4.53.3", 3569 + "@rollup/rollup-freebsd-x64": "4.53.3", 3570 + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", 3571 + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", 3572 + "@rollup/rollup-linux-arm64-gnu": "4.53.3", 3573 + "@rollup/rollup-linux-arm64-musl": "4.53.3", 3574 + "@rollup/rollup-linux-loong64-gnu": "4.53.3", 3575 + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", 3576 + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", 3577 + "@rollup/rollup-linux-riscv64-musl": "4.53.3", 3578 + "@rollup/rollup-linux-s390x-gnu": "4.53.3", 3579 + "@rollup/rollup-linux-x64-gnu": "4.53.3", 3580 + "@rollup/rollup-linux-x64-musl": "4.53.3", 3581 + "@rollup/rollup-openharmony-arm64": "4.53.3", 3582 + "@rollup/rollup-win32-arm64-msvc": "4.53.3", 3583 + "@rollup/rollup-win32-ia32-msvc": "4.53.3", 3584 + "@rollup/rollup-win32-x64-gnu": "4.53.3", 3585 + "@rollup/rollup-win32-x64-msvc": "4.53.3", 3586 "fsevents": "~2.3.2" 3587 } 3588 }, 3589 "node_modules/rollup-plugin-typescript2": { 3590 "version": "0.36.0", 3591 "dev": true, 3592 "license": "MIT", 3593 "dependencies": { ··· 3602 "typescript": ">=2.4.0" 3603 } 3604 }, 3605 "node_modules/rollup-plugin-typescript2/node_modules/semver": { 3606 "version": "7.7.3", 3607 "dev": true, 3608 "license": "ISC", 3609 "bin": { ··· 3613 "node": ">=10" 3614 } 3615 }, 3616 "node_modules/scheduler": { 3617 "version": "0.27.0", 3618 "dev": true, 3619 "license": "MIT" 3620 }, 3621 "node_modules/semver": { 3622 + "version": "7.5.4", 3623 "dev": true, 3624 "license": "ISC", 3625 + "dependencies": { 3626 + "lru-cache": "^6.0.0" 3627 + }, 3628 "bin": { 3629 "semver": "bin/semver.js" 3630 + }, 3631 + "engines": { 3632 + "node": ">=10" 3633 } 3634 }, 3635 "node_modules/shebang-command": { 3636 "version": "2.0.0", 3637 "dev": true, 3638 "license": "MIT", 3639 "dependencies": { ··· 3645 }, 3646 "node_modules/shebang-regex": { 3647 "version": "3.0.0", 3648 "dev": true, 3649 "license": "MIT", 3650 "engines": { ··· 3653 }, 3654 "node_modules/source-map": { 3655 "version": "0.6.1", 3656 "dev": true, 3657 "license": "BSD-3-Clause", 3658 "engines": { ··· 3661 }, 3662 "node_modules/source-map-js": { 3663 "version": "1.2.1", 3664 "dev": true, 3665 "license": "BSD-3-Clause", 3666 "engines": { ··· 3669 }, 3670 "node_modules/source-map-support": { 3671 "version": "0.5.21", 3672 "dev": true, 3673 "license": "MIT", 3674 "optional": true, 3675 "dependencies": { 3676 "buffer-from": "^1.0.0", 3677 "source-map": "^0.6.0" ··· 3679 }, 3680 "node_modules/sprintf-js": { 3681 "version": "1.0.3", 3682 "dev": true, 3683 "license": "BSD-3-Clause" 3684 }, 3685 "node_modules/string-argv": { 3686 "version": "0.3.2", 3687 "dev": true, 3688 "license": "MIT", 3689 "engines": { ··· 3692 }, 3693 "node_modules/strip-json-comments": { 3694 "version": "3.1.1", 3695 "dev": true, 3696 "license": "MIT", 3697 "engines": { ··· 3702 } 3703 }, 3704 "node_modules/supports-color": { 3705 + "version": "8.1.1", 3706 "dev": true, 3707 "license": "MIT", 3708 "dependencies": { 3709 "has-flag": "^4.0.0" 3710 }, 3711 "engines": { 3712 + "node": ">=10" 3713 + }, 3714 + "funding": { 3715 + "url": "https://github.com/chalk/supports-color?sponsor=1" 3716 } 3717 }, 3718 "node_modules/supports-preserve-symlinks-flag": { 3719 "version": "1.0.0", 3720 "dev": true, 3721 "license": "MIT", 3722 "engines": { ··· 3726 "url": "https://github.com/sponsors/ljharb" 3727 } 3728 }, 3729 "node_modules/tinyglobby": { 3730 "version": "0.2.15", 3731 "dev": true, 3732 "license": "MIT", 3733 "dependencies": { ··· 3741 "url": "https://github.com/sponsors/SuperchupuDev" 3742 } 3743 }, 3744 "node_modules/ts-api-utils": { 3745 "version": "2.1.0", 3746 "dev": true, 3747 "license": "MIT", 3748 "engines": { ··· 3754 }, 3755 "node_modules/tslib": { 3756 "version": "2.8.1", 3757 "dev": true, 3758 "license": "0BSD" 3759 }, 3760 "node_modules/type-check": { 3761 "version": "0.4.0", 3762 "dev": true, 3763 "license": "MIT", 3764 "dependencies": { ··· 3774 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3775 "dev": true, 3776 "license": "Apache-2.0", 3777 + "peer": true, 3778 "bin": { 3779 "tsc": "bin/tsc", 3780 "tsserver": "bin/tsserver" ··· 3784 } 3785 }, 3786 "node_modules/typescript-eslint": { 3787 + "version": "8.48.1", 3788 "dev": true, 3789 "license": "MIT", 3790 "dependencies": { 3791 + "@typescript-eslint/eslint-plugin": "8.48.1", 3792 + "@typescript-eslint/parser": "8.48.1", 3793 + "@typescript-eslint/typescript-estree": "8.48.1", 3794 + "@typescript-eslint/utils": "8.48.1" 3795 }, 3796 "engines": { 3797 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 3807 }, 3808 "node_modules/ufo": { 3809 "version": "1.6.1", 3810 "dev": true, 3811 "license": "MIT" 3812 }, 3813 "node_modules/undici-types": { 3814 + "version": "7.16.0", 3815 "dev": true, 3816 "license": "MIT" 3817 }, 3818 "node_modules/universalify": { 3819 "version": "2.0.1", 3820 "dev": true, 3821 "license": "MIT", 3822 "engines": { ··· 3824 } 3825 }, 3826 "node_modules/unplugin": { 3827 + "version": "2.3.11", 3828 "dev": true, 3829 "license": "MIT", 3830 "dependencies": { ··· 3839 }, 3840 "node_modules/unplugin-dts": { 3841 "version": "1.0.0-beta.6", 3842 "dev": true, 3843 "license": "MIT", 3844 "dependencies": { ··· 3889 } 3890 } 3891 }, 3892 + "node_modules/unplugin-dts/node_modules/@rollup/pluginutils": { 3893 + "version": "5.3.0", 3894 "dev": true, 3895 "license": "MIT", 3896 + "dependencies": { 3897 + "@types/estree": "^1.0.0", 3898 + "estree-walker": "^2.0.2", 3899 + "picomatch": "^4.0.2" 3900 + }, 3901 "engines": { 3902 + "node": ">=14.0.0" 3903 + }, 3904 + "peerDependencies": { 3905 + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 3906 }, 3907 + "peerDependenciesMeta": { 3908 + "rollup": { 3909 + "optional": true 3910 + } 3911 } 3912 }, 3913 "node_modules/update-browserslist-db": { 3914 + "version": "1.1.4", 3915 "dev": true, 3916 "funding": [ 3917 { ··· 3941 }, 3942 "node_modules/uri-js": { 3943 "version": "4.4.1", 3944 "dev": true, 3945 "license": "BSD-2-Clause", 3946 "dependencies": { ··· 3950 "node_modules/vite": { 3951 "name": "rolldown-vite", 3952 "version": "7.1.14", 3953 "dev": true, 3954 "license": "MIT", 3955 + "peer": true, 3956 "dependencies": { 3957 "@oxc-project/runtime": "0.92.0", 3958 "fdir": "^6.5.0", ··· 4023 } 4024 } 4025 }, 4026 "node_modules/vscode-uri": { 4027 "version": "3.1.0", 4028 "dev": true, 4029 "license": "MIT" 4030 }, 4031 "node_modules/webpack-virtual-modules": { 4032 "version": "0.6.2", 4033 "dev": true, 4034 "license": "MIT" 4035 }, 4036 "node_modules/which": { 4037 "version": "2.0.2", 4038 "dev": true, 4039 "license": "ISC", 4040 "dependencies": { ··· 4049 }, 4050 "node_modules/word-wrap": { 4051 "version": "1.2.5", 4052 "dev": true, 4053 "license": "MIT", 4054 "engines": { ··· 4056 } 4057 }, 4058 "node_modules/yallist": { 4059 + "version": "4.0.0", 4060 "dev": true, 4061 "license": "ISC" 4062 }, 4063 "node_modules/yocto-queue": { 4064 "version": "0.1.0", 4065 "dev": true, 4066 "license": "MIT", 4067 "engines": {
+3 -4
package.json
··· 1 { 2 "name": "atproto-ui", 3 - "version": "0.5.5", 4 "type": "module", 5 "description": "React components and hooks for rendering AT Protocol records.", 6 "main": "./lib-dist/index.js", ··· 18 "README.md" 19 ], 20 "sideEffects": [ 21 - "./lib-dist/styles.css", 22 - "./lib-dist/index.js" 23 ], 24 "scripts": { 25 "dev": "vite", ··· 44 "@atcute/bluesky": "^3.2.3", 45 "@atcute/client": "^4.0.3", 46 "@atcute/identity-resolver": "^1.1.3", 47 - "@atcute/tangled": "^1.0.6" 48 }, 49 "devDependencies": { 50 "@eslint/js": "^9.36.0",
··· 1 { 2 "name": "atproto-ui", 3 + "version": "0.12.0", 4 "type": "module", 5 "description": "React components and hooks for rendering AT Protocol records.", 6 "main": "./lib-dist/index.js", ··· 18 "README.md" 19 ], 20 "sideEffects": [ 21 + "./lib-dist/styles.css" 22 ], 23 "scripts": { 24 "dev": "vite", ··· 43 "@atcute/bluesky": "^3.2.3", 44 "@atcute/client": "^4.0.3", 45 "@atcute/identity-resolver": "^1.1.3", 46 + "@atcute/tangled": "^1.0.10" 47 }, 48 "devDependencies": { 49 "@eslint/js": "^9.36.0",
+118 -2
src/App.tsx
··· 1 import React, { useState, useCallback, useRef } from "react"; 2 - import { AtProtoProvider } from "../lib"; 3 - import "../lib/styles.css" 4 import "./App.css"; 5 6 import { TangledString } from "../lib/components/TangledString"; ··· 12 } from "../lib/components/BlueskyPost"; 13 import { BlueskyPostList } from "../lib/components/BlueskyPostList"; 14 import { BlueskyQuotePost } from "../lib/components/BlueskyQuotePost"; 15 import { useDidResolution } from "../lib/hooks/useDidResolution"; 16 import { useLatestRecord } from "../lib/hooks/useLatestRecord"; 17 import type { FeedPostRecord } from "../lib/types/bluesky"; ··· 42 // Pass prefetched recordโ€”BlueskyPost won't re-fetch it 43 return <BlueskyPost did={did} rkey={rkey} record={record} />; 44 };`; 45 46 const codeBlockBase: React.CSSProperties = { 47 fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace', ··· 255 <h3 style={sectionHeaderStyle}>Recent Posts</h3> 256 <BlueskyPostList did={did} /> 257 </section> 258 </div> 259 <div style={columnStackStyle}> 260 <section style={panelStyle}> ··· 355 /> 356 </section> 357 <section style={panelStyle}> 358 <h3 style={sectionHeaderStyle}> 359 Custom Themed Post 360 </h3> ··· 460 style={codeTextStyle} 461 > 462 {prefetchedDataSnippet} 463 </code> 464 </pre> 465 </section>
··· 1 import React, { useState, useCallback, useRef } from "react"; 2 + import { AtProtoProvider, TangledRepo } from "../lib"; 3 + import "../lib/styles.css"; 4 import "./App.css"; 5 6 import { TangledString } from "../lib/components/TangledString"; ··· 12 } from "../lib/components/BlueskyPost"; 13 import { BlueskyPostList } from "../lib/components/BlueskyPostList"; 14 import { BlueskyQuotePost } from "../lib/components/BlueskyQuotePost"; 15 + import { GrainGallery } from "../lib/components/GrainGallery"; 16 + import { CurrentlyPlaying } from "../lib/components/CurrentlyPlaying"; 17 + import { LastPlayed } from "../lib/components/LastPlayed"; 18 + import { SongHistoryList } from "../lib/components/SongHistoryList"; 19 import { useDidResolution } from "../lib/hooks/useDidResolution"; 20 import { useLatestRecord } from "../lib/hooks/useLatestRecord"; 21 import type { FeedPostRecord } from "../lib/types/bluesky"; ··· 46 // Pass prefetched recordโ€”BlueskyPost won't re-fetch it 47 return <BlueskyPost did={did} rkey={rkey} record={record} />; 48 };`; 49 + 50 + const atcuteUsageSnippet = `import { Client, simpleFetchHandler, ok } from '@atcute/client'; 51 + import type { AppBskyFeedPost } from '@atcute/bluesky'; 52 + import { BlueskyPost } from 'atproto-ui'; 53 + 54 + // Create atcute client 55 + const client = new Client({ 56 + handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 57 + }); 58 + 59 + // Fetch a record 60 + const data = await ok( 61 + client.get('com.atproto.repo.getRecord', { 62 + params: { 63 + repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq', 64 + collection: 'app.bsky.feed.post', 65 + rkey: '3m45rq4sjes2h' 66 + } 67 + }) 68 + ); 69 + 70 + const record = data.value as AppBskyFeedPost.Main; 71 + 72 + // Pass atcute record directly to component! 73 + <BlueskyPost 74 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 75 + rkey="3m45rq4sjes2h" 76 + record={record} 77 + />`; 78 79 const codeBlockBase: React.CSSProperties = { 80 fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace', ··· 288 <h3 style={sectionHeaderStyle}>Recent Posts</h3> 289 <BlueskyPostList did={did} /> 290 </section> 291 + <section style={panelStyle}> 292 + <h3 style={sectionHeaderStyle}> 293 + grain.social Gallery Demo 294 + </h3> 295 + <p 296 + style={{ 297 + fontSize: 12, 298 + color: `var(--demo-text-secondary)`, 299 + margin: "0 0 8px", 300 + }} 301 + > 302 + Instagram-style photo gallery from grain.social 303 + </p> 304 + <GrainGallery 305 + did="kat.meangirls.online" 306 + rkey="3m2e2qikseq2f" 307 + /> 308 + </section> 309 + <section style={panelStyle}> 310 + <h3 style={sectionHeaderStyle}> 311 + teal.fm Currently Playing 312 + </h3> 313 + <p 314 + style={{ 315 + fontSize: 12, 316 + color: `var(--demo-text-secondary)`, 317 + margin: "0 0 8px", 318 + }} 319 + > 320 + Currently playing track from teal.fm (refreshes every 15s) 321 + </p> 322 + <CurrentlyPlaying did="nekomimi.pet" /> 323 + </section> 324 + <section style={panelStyle}> 325 + <h3 style={sectionHeaderStyle}> 326 + teal.fm Last Played 327 + </h3> 328 + <p 329 + style={{ 330 + fontSize: 12, 331 + color: `var(--demo-text-secondary)`, 332 + margin: "0 0 8px", 333 + }} 334 + > 335 + Most recent play from teal.fm feed 336 + </p> 337 + <LastPlayed did="nekomimi.pet" /> 338 + </section> 339 + <section style={panelStyle}> 340 + <h3 style={sectionHeaderStyle}> 341 + teal.fm Song History 342 + </h3> 343 + <p 344 + style={{ 345 + fontSize: 12, 346 + color: `var(--demo-text-secondary)`, 347 + margin: "0 0 8px", 348 + }} 349 + > 350 + Listening history with album art focus 351 + </p> 352 + <SongHistoryList did="nekomimi.pet" limit={6} /> 353 + </section> 354 </div> 355 <div style={columnStackStyle}> 356 <section style={panelStyle}> ··· 451 /> 452 </section> 453 <section style={panelStyle}> 454 + <TangledRepo 455 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 456 + rkey="3m2sx5zpxzs22" 457 + /> 458 + </section> 459 + <section style={panelStyle}> 460 <h3 style={sectionHeaderStyle}> 461 Custom Themed Post 462 </h3> ··· 562 style={codeTextStyle} 563 > 564 {prefetchedDataSnippet} 565 + </code> 566 + </pre> 567 + <p 568 + style={{ 569 + color: `var(--demo-text-secondary)`, 570 + margin: "16px 0 8px", 571 + }} 572 + > 573 + Use atcute directly to construct records and pass them to 574 + componentsโ€”fully compatible! 575 + </p> 576 + <pre style={codeBlockStyle}> 577 + <code className="language-tsx" style={codeTextStyle}> 578 + {atcuteUsageSnippet} 579 </code> 580 </pre> 581 </section>
+2 -1
tsconfig.lib.json
··· 18 "declarationDir": "dist-lib", 19 "sourceMap": true, 20 "outDir": "./lib-dist", 21 - "rootDir": "./lib" 22 }, 23 "include": ["lib/**/*.ts", "lib/**/*.tsx"] 24 }
··· 18 "declarationDir": "dist-lib", 19 "sourceMap": true, 20 "outDir": "./lib-dist", 21 + "rootDir": "./lib", 22 + "types": ["@atcute/bluesky", "@atcute/tangled"] 23 }, 24 "include": ["lib/**/*.ts", "lib/**/*.tsx"] 25 }
+2 -3
vite.config.ts
··· 35 rollupOptions: { 36 input: resolve(__dirname, 'index.html') 37 }, 38 - sourcemap: true 39 } : { 40 // Library build configuration 41 lib: { 42 entry: resolve(__dirname, 'lib/index.ts'), 43 name: 'atproto-ui', 44 formats: ['es'], 45 fileName: 'atproto-ui' ··· 71 } 72 } 73 }, 74 - sourcemap: true, 75 - minify: false 76 } 77 });
··· 35 rollupOptions: { 36 input: resolve(__dirname, 'index.html') 37 }, 38 + sourcemap: false 39 } : { 40 // Library build configuration 41 lib: { 42 entry: resolve(__dirname, 'lib/index.ts'), 43 + cssFileName: resolve(__dirname, 'lib/styles.css'), 44 name: 'atproto-ui', 45 formats: ['es'], 46 fileName: 'atproto-ui' ··· 72 } 73 } 74 }, 75 } 76 });