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 + ```
+4 -3
README.md
··· 1 1 # atproto-ui 2 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.netlify.app). 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 4 5 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 6 ··· 11 11 12 12 ## Features 13 13 14 - - **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledString`, `LeafletDocument`) 14 + - **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledRepo`, `LeafletDocument`) 15 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 16 17 - **Customizable theming** - Override CSS variables to match your app's design 17 18 - **Composable hooks** - Build custom renderers with protocol primitives 18 19 - Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients ··· 218 219 219 220 ## Demo 220 221 221 - Check out the [live demo](https://atproto-ui.netlify.app/) to see all components in action. 222 + Check out the [live demo](https://atproto-ui.wisp.place/) to see all components in action. 222 223 223 224 ### Running Locally 224 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 + }
+306 -111
lib/components/BlueskyPostList.tsx
··· 4 4 type AuthorFeedReason, 5 5 type ReplyParentInfo, 6 6 } from "../hooks/usePaginatedRecords"; 7 - import type { FeedPostRecord } from "../types/bluesky"; 7 + import type { FeedPostRecord, ProfileRecord } from "../types/bluesky"; 8 8 import { useDidResolution } from "../hooks/useDidResolution"; 9 9 import { BlueskyIcon } from "./BlueskyIcon"; 10 10 import { parseAtUri } from "../utils/at-uri"; 11 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"; 12 18 13 19 /** 14 20 * Options for rendering a paginated list of Bluesky posts. ··· 73 79 74 80 if (error) 75 81 return ( 76 - <div style={{ padding: 8, color: "crimson" }}> 82 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 77 83 Failed to load posts. 78 84 </div> 79 85 ); ··· 117 123 record={record.value} 118 124 rkey={record.rkey} 119 125 did={actorPath} 126 + uri={record.uri} 120 127 reason={record.reason} 121 128 replyParent={record.replyParent} 122 129 hasDivider={idx < records.length - 1} ··· 203 210 record: FeedPostRecord; 204 211 rkey: string; 205 212 did: string; 213 + uri?: string; 206 214 reason?: AuthorFeedReason; 207 215 replyParent?: ReplyParentInfo; 208 216 hasDivider: boolean; ··· 212 220 record, 213 221 rkey, 214 222 did, 223 + uri, 215 224 reason, 216 225 replyParent, 217 226 hasDivider, ··· 224 233 const absolute = record.createdAt 225 234 ? new Date(record.createdAt).toLocaleString() 226 235 : undefined; 227 - const href = `${blueskyAppBaseUrl}/profile/${did}/post/${rkey}`; 228 - const repostLabel = 229 - reason?.$type === "app.bsky.feed.defs#reasonRepost" 230 - ? `${formatActor(reason.by) ?? "Someone"} reposted` 231 - : 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 232 280 const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 233 - const parentDid = 234 - replyParent?.author?.did ?? 235 - (parentUri ? parseAtUri(parentUri)?.did : undefined); 236 - const { handle: resolvedReplyHandle } = useDidResolution( 281 + const parentDid = replyParent?.author?.did ?? (parentUri ? parseAtUri(parentUri)?.did : undefined); 282 + const { handle: parentHandle } = useDidResolution( 237 283 replyParent?.author?.handle ? undefined : parentDid, 238 284 ); 239 - const replyLabel = formatReplyTarget( 240 - parentUri, 241 - replyParent, 242 - resolvedReplyHandle, 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, 243 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}`; 244 306 245 307 return ( 246 - <a 247 - href={href} 248 - target="_blank" 249 - rel="noopener noreferrer" 308 + <div 250 309 style={{ 251 - ...listStyles.row, 252 - color: `var(--atproto-color-text)`, 253 - borderBottom: hasDivider 254 - ? `1px solid var(--atproto-color-border)` 255 - : "none", 310 + ...listStyles.rowContainer, 311 + borderBottom: hasDivider ? `1px solid var(--atproto-color-border)` : "none", 256 312 }} 257 313 > 258 - {repostLabel && ( 259 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 260 - {repostLabel} 261 - </span> 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> 262 342 )} 263 - {replyLabel && ( 264 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 265 - {replyLabel} 266 - </span> 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> 267 375 )} 268 - {relative && ( 269 - <span 270 - style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)` }} 271 - title={absolute} 272 - > 273 - {relative} 274 - </span> 275 - )} 276 - {text && ( 277 - <p style={{ ...listStyles.rowBody, color: `var(--atproto-color-text)` }}> 278 - {text} 279 - </p> 280 - )} 281 - {!text && ( 282 - <p 283 - style={{ 284 - ...listStyles.rowBody, 285 - color: `var(--atproto-color-text)`, 286 - fontStyle: "italic", 287 - }} 288 - > 289 - No text content. 290 - </p> 291 - )} 292 - </a> 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> 293 436 ); 294 437 }; 295 438 ··· 354 497 display: "flex", 355 498 alignItems: "center", 356 499 justifyContent: "center", 357 - //background: 'rgba(17, 133, 254, 0.14)', 358 500 borderRadius: "50%", 359 501 } satisfies React.CSSProperties, 360 502 headerText: { ··· 382 524 fontSize: 13, 383 525 textAlign: "center", 384 526 } satisfies React.CSSProperties, 385 - row: { 386 - padding: "18px", 387 - textDecoration: "none", 527 + rowContainer: { 528 + padding: "16px", 388 529 display: "flex", 389 530 flexDirection: "column", 390 - gap: 6, 531 + gap: 8, 391 532 transition: "background-color 120ms ease", 533 + position: "relative", 392 534 } satisfies React.CSSProperties, 393 - rowHeader: { 535 + repostIndicator: { 394 536 display: "flex", 395 - gap: 6, 396 - alignItems: "baseline", 537 + alignItems: "center", 538 + gap: 8, 397 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", 398 549 } satisfies React.CSSProperties, 399 - rowTime: { 400 - fontSize: 12, 550 + repostText: { 551 + fontSize: 13, 401 552 fontWeight: 500, 402 553 } satisfies React.CSSProperties, 403 - rowMeta: { 404 - fontSize: 12, 554 + replyIndicator: { 555 + display: "flex", 556 + alignItems: "center", 557 + gap: 8, 558 + fontSize: 13, 405 559 fontWeight: 500, 406 - letterSpacing: "0.6px", 560 + paddingLeft: 8, 561 + marginBottom: 4, 407 562 } satisfies React.CSSProperties, 408 - rowBody: { 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: { 409 641 margin: 0, 410 642 whiteSpace: "pre-wrap", 411 - fontSize: 14, 412 - lineHeight: 1.45, 643 + fontSize: 15, 644 + lineHeight: 1.5, 645 + wordBreak: "break-word", 413 646 } satisfies React.CSSProperties, 414 647 footer: { 415 648 display: "flex", ··· 418 651 padding: "12px 18px", 419 652 borderTop: "1px solid transparent", 420 653 fontSize: 13, 421 - } satisfies React.CSSProperties, 422 - navButton: { 423 - border: "none", 424 - borderRadius: 999, 425 - padding: "6px 12px", 426 - fontSize: 13, 427 - fontWeight: 500, 428 - background: "transparent", 429 - display: "flex", 430 - alignItems: "center", 431 - gap: 4, 432 - transition: "background-color 120ms ease", 433 654 } satisfies React.CSSProperties, 434 655 pageChips: { 435 656 display: "flex", ··· 474 695 }; 475 696 476 697 export default BlueskyPostList; 477 - 478 - function formatActor(actor?: { handle?: string; did?: string }) { 479 - if (!actor) return undefined; 480 - if (actor.handle) return `@${actor.handle}`; 481 - if (actor.did) return `@${formatDid(actor.did)}`; 482 - return undefined; 483 - } 484 - 485 - function formatReplyTarget( 486 - parentUri?: string, 487 - feedParent?: ReplyParentInfo, 488 - resolvedHandle?: string, 489 - ) { 490 - const directHandle = feedParent?.author?.handle; 491 - const handle = directHandle ?? resolvedHandle; 492 - if (handle) { 493 - return `Replying to @${handle}`; 494 - } 495 - const parentDid = feedParent?.author?.did; 496 - const targetUri = feedParent?.uri ?? parentUri; 497 - if (!targetUri) return undefined; 498 - const parsed = parseAtUri(targetUri); 499 - const did = parentDid ?? parsed?.did; 500 - if (!did) return undefined; 501 - return `Replying to @${formatDid(did)}`; 502 - }
+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;
+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;
+1 -1
lib/components/TangledString.tsx
··· 1 1 import React from "react"; 2 2 import { AtProtoRecord } from "../core/AtProtoRecord"; 3 3 import { TangledStringRenderer } from "../renderers/TangledStringRenderer"; 4 - import type { TangledStringRecord } from "../renderers/TangledStringRenderer"; 4 + import type { TangledStringRecord } from "../types/tangled"; 5 5 import { useAtProto } from "../providers/AtProtoProvider"; 6 6 7 7 /**
+68 -6
lib/core/AtProtoRecord.tsx
··· 1 - import React from "react"; 1 + import React, { useState, useEffect, useRef } from "react"; 2 2 import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 3 3 4 4 /** ··· 15 15 fallback?: React.ReactNode; 16 16 /** React node shown while the record is being fetched. */ 17 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; 18 22 } 19 23 20 24 /** ··· 61 65 * 62 66 * When no custom renderer is provided, displays the record as formatted JSON. 63 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 + * 64 71 * @example 65 72 * ```tsx 66 73 * // Fetch mode - retrieves record from network ··· 81 88 * /> 82 89 * ``` 83 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 + * 84 104 * @param props - Either fetch props (did/collection/rkey) or prefetch props (record). 85 105 * @returns A rendered AT Protocol record with loading/error states handled. 86 106 */ ··· 89 109 renderer: Renderer, 90 110 fallback = null, 91 111 loadingIndicator = "Loadingโ€ฆ", 112 + refreshInterval, 113 + compareRecords, 92 114 } = props; 93 115 const hasProvidedRecord = "record" in props; 94 116 const providedRecord = hasProvidedRecord ? props.record : undefined; 95 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 + 96 139 const { 97 140 record: fetchedRecord, 98 141 error, 99 142 loading, 100 143 } = useAtProtoRecord<T>({ 101 - did: hasProvidedRecord ? undefined : props.did, 102 - collection: hasProvidedRecord ? undefined : props.collection, 103 - rkey: hasProvidedRecord ? undefined : props.rkey, 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 104 149 }); 105 150 106 - const record = providedRecord ?? fetchedRecord; 107 - const isLoading = loading && !providedRecord; 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; 108 170 109 171 if (error && !record) return <>{fallback}</>; 110 172 if (!record) return <>{isLoading ? loadingIndicator : fallback}</>;
+88 -8
lib/hooks/useAtProtoRecord.ts
··· 15 15 collection?: string; 16 16 /** Record key string uniquely identifying the record within the collection. */ 17 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; 18 22 } 19 23 20 24 /** ··· 42 46 * @param did - DID (or handle before resolution) that owns the record. 43 47 * @param collection - NSID collection from which to fetch the record. 44 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. 45 51 * @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag. 46 52 */ 47 53 export function useAtProtoRecord<T = unknown>({ 48 54 did: handleOrDid, 49 55 collection, 50 56 rkey, 57 + bypassCache = false, 58 + _refreshKey = 0, 51 59 }: AtProtoRecordKey): AtProtoRecordState<T> { 52 60 const { recordCache } = useAtProto(); 53 61 const isBlueskyCollection = collection?.startsWith("app.bsky."); ··· 133 141 134 142 assignState({ loading: true, error: undefined, record: undefined }); 135 143 136 - // Use recordCache.ensure for deduplication and caching 137 - const { promise, release } = recordCache.ensure<T>( 138 - did, 139 - collection, 140 - rkey, 141 - () => { 142 - const controller = new AbortController(); 144 + // Bypass cache if requested (for auto-refresh scenarios) 145 + if (bypassCache) { 146 + assignState({ loading: true, error: undefined }); 143 147 144 - const fetchPromise = (async () => { 148 + // Skip cache and fetch directly 149 + const controller = new AbortController(); 150 + 151 + const fetchPromise = (async () => { 152 + try { 145 153 const { rpc } = await createAtprotoClient({ 146 154 service: endpoint, 147 155 }); ··· 163 171 }); 164 172 if (!res.ok) throw new Error("Failed to load record"); 165 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 + } 166 244 })(); 167 245 168 246 return { ··· 205 283 didError, 206 284 endpointError, 207 285 recordCache, 286 + bypassCache, 287 + _refreshKey, 208 288 ]); 209 289 210 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 + }
+40 -15
lib/hooks/useBlueskyAppview.ts
··· 236 236 }: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> { 237 237 const { recordCache, blueskyAppviewService, resolver } = useAtProto(); 238 238 const effectiveAppviewService = appviewService ?? blueskyAppviewService; 239 + 240 + // Only use this hook for Bluesky collections (app.bsky.*) 241 + const isBlueskyCollection = collection?.startsWith("app.bsky."); 242 + 239 243 const { 240 244 did, 241 245 error: didError, ··· 270 274 } 271 275 }; 272 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 + } 273 289 274 290 if (didError) { 275 291 if (!cancelled) dispatch({ type: "SET_ERROR", error: didError }); ··· 308 324 dispatch({ type: "SET_LOADING", loading: true }); 309 325 310 326 // Use recordCache.ensure for deduplication and caching 311 - const { promise, release } = recordCache.ensure<T>( 327 + const { promise, release } = recordCache.ensure<{ record: T; source: "appview" | "slingshot" | "pds" }>( 312 328 did, 313 329 collection, 314 330 rkey, 315 331 () => { 316 332 const controller = new AbortController(); 317 333 318 - const fetchPromise = (async () => { 334 + const fetchPromise = (async (): Promise<{ record: T; source: "appview" | "slingshot" | "pds" }> => { 319 335 let lastError: Error | undefined; 320 336 321 337 // Tier 1: Try Bluesky appview API ··· 328 344 effectiveAppviewService, 329 345 ); 330 346 if (result) { 331 - return result; 347 + return { record: result, source: "appview" }; 332 348 } 333 349 } catch (err) { 334 350 lastError = err as Error; ··· 341 357 const slingshotUrl = resolver.getSlingshotUrl(); 342 358 const result = await fetchFromSlingshot<T>(did, collection, rkey, slingshotUrl); 343 359 if (result) { 344 - return result; 360 + return { record: result, source: "slingshot" }; 345 361 } 346 362 } catch (err) { 347 363 lastError = err as Error; ··· 357 373 pdsEndpoint, 358 374 ); 359 375 if (result) { 360 - return result; 376 + return { record: result, source: "pds" }; 361 377 } 362 378 } catch (err) { 363 379 lastError = err as Error; 364 380 } 365 381 366 - // All tiers failed 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 + 367 389 throw lastError ?? new Error("Failed to fetch record from all sources"); 368 390 })(); 369 391 ··· 377 399 releaseRef.current = release; 378 400 379 401 promise 380 - .then((record) => { 402 + .then(({ record, source }) => { 381 403 if (!cancelled) { 382 404 dispatch({ 383 405 type: "SET_SUCCESS", 384 406 record, 385 - source: "appview", 407 + source, 386 408 }); 387 409 } 388 410 }) ··· 677 699 }; 678 700 }> { 679 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 + 680 711 return await (rpc as unknown as { 681 712 get: ( 682 713 nsid: string, ··· 689 720 }; 690 721 }>; 691 722 }).get("com.atproto.repo.listRecords", { 692 - params: { 693 - repo: did, 694 - collection, 695 - limit, 696 - cursor, 697 - reverse: false, 698 - }, 723 + params, 699 724 }); 700 725 } 701 726
+5 -2
lib/hooks/useLatestRecord.ts
··· 21 21 22 22 /** 23 23 * Fetches the most recent record from a collection using `listRecords(limit=3)`. 24 - * 24 + * 25 25 * Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly. 26 - * 26 + * 27 27 * Records with invalid timestamps (before 2023, when ATProto was created) are automatically 28 28 * skipped, and additional records are fetched to find a valid one. 29 29 * 30 30 * @param handleOrDid - Handle or DID that owns the collection. 31 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. 32 33 * @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error. 33 34 */ 34 35 export function useLatestRecord<T = unknown>( 35 36 handleOrDid: string | undefined, 36 37 collection: string, 38 + refreshKey?: number, 37 39 ): LatestRecordState<T> { 38 40 const { 39 41 did, ··· 157 159 resolvingEndpoint, 158 160 didError, 159 161 endpointError, 162 + refreshKey, 160 163 ]); 161 164 162 165 return state;
+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 12 export * from "./components/BlueskyPostList"; 13 13 export * from "./components/BlueskyProfile"; 14 14 export * from "./components/BlueskyQuotePost"; 15 + export * from "./components/GrainGallery"; 15 16 export * from "./components/LeafletDocument"; 17 + export * from "./components/TangledRepo"; 16 18 export * from "./components/TangledString"; 19 + export * from "./components/CurrentlyPlaying"; 20 + export * from "./components/LastPlayed"; 21 + export * from "./components/SongHistoryList"; 17 22 18 23 // Hooks 19 24 export * from "./hooks/useAtProtoRecord"; 25 + export * from "./hooks/useBacklinks"; 20 26 export * from "./hooks/useBlob"; 21 27 export * from "./hooks/useBlueskyAppview"; 22 28 export * from "./hooks/useBlueskyProfile"; ··· 24 30 export * from "./hooks/useLatestRecord"; 25 31 export * from "./hooks/usePaginatedRecords"; 26 32 export * from "./hooks/usePdsEndpoint"; 33 + export * from "./hooks/useRepoLanguages"; 27 34 28 35 // Renderers 29 36 export * from "./renderers/BlueskyPostRenderer"; 30 37 export * from "./renderers/BlueskyProfileRenderer"; 38 + export * from "./renderers/GrainGalleryRenderer"; 31 39 export * from "./renderers/LeafletDocumentRenderer"; 40 + export * from "./renderers/TangledRepoRenderer"; 32 41 export * from "./renderers/TangledStringRenderer"; 42 + export * from "./renderers/CurrentlyPlayingRenderer"; 33 43 34 44 // Types 35 45 export * from "./types/bluesky"; 46 + export * from "./types/grain"; 36 47 export * from "./types/leaflet"; 48 + export * from "./types/tangled"; 49 + export * from "./types/teal"; 37 50 export * from "./types/theme"; 38 51 39 52 // Utilities
+16 -1
lib/providers/AtProtoProvider.tsx
··· 26 26 blueskyAppBaseUrl?: string; 27 27 /** Optional custom Tangled base URL for links. Defaults to https://tangled.org */ 28 28 tangledBaseUrl?: string; 29 + /** Optional custom Constellation API URL for backlinks. Defaults to https://constellation.microcosm.blue */ 30 + constellationBaseUrl?: string; 29 31 } 30 32 31 33 /** ··· 42 44 blueskyAppBaseUrl: string; 43 45 /** Normalized Tangled base URL for links. */ 44 46 tangledBaseUrl: string; 47 + /** Normalized Constellation API base URL for backlinks. */ 48 + constellationBaseUrl: string; 45 49 /** Cache for DID documents and handle mappings. */ 46 50 didCache: DidCache; 47 51 /** Cache for fetched blob data. */ ··· 98 102 blueskyAppviewService, 99 103 blueskyAppBaseUrl, 100 104 tangledBaseUrl, 105 + constellationBaseUrl, 101 106 }: AtProtoProviderProps) { 102 107 const normalizedPlc = useMemo( 103 108 () => ··· 153 158 ), 154 159 [tangledBaseUrl], 155 160 ); 161 + const normalizedConstellation = useMemo( 162 + () => 163 + normalizeBaseUrl( 164 + constellationBaseUrl && constellationBaseUrl.trim() 165 + ? constellationBaseUrl 166 + : DEFAULT_CONFIG.constellationBaseUrl, 167 + ), 168 + [constellationBaseUrl], 169 + ); 156 170 const resolver = useMemo( 157 171 () => new ServiceResolver({ 158 172 plcDirectory: normalizedPlc, ··· 181 195 blueskyAppviewService: normalizedAppview, 182 196 blueskyAppBaseUrl: normalizedBlueskyApp, 183 197 tangledBaseUrl: normalizedTangled, 198 + constellationBaseUrl: normalizedConstellation, 184 199 didCache: cachesRef.current!.didCache, 185 200 blobCache: cachesRef.current!.blobCache, 186 201 recordCache: cachesRef.current!.recordCache, 187 202 }), 188 - [resolver, normalizedPlc, normalizedAppview, normalizedBlueskyApp, normalizedTangled], 203 + [resolver, normalizedPlc, normalizedAppview, normalizedBlueskyApp, normalizedTangled, normalizedConstellation], 189 204 ); 190 205 191 206 return (
+8 -7
lib/renderers/BlueskyPostRenderer.tsx
··· 56 56 57 57 if (error) { 58 58 return ( 59 - <div style={{ padding: 8, color: "crimson" }}> 59 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 60 60 Failed to load post. 61 61 </div> 62 62 ); 63 63 } 64 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 64 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 65 65 66 66 const text = record.text; 67 67 const createdDate = new Date(record.createdAt); ··· 181 181 </div> 182 182 ); 183 183 184 - const Avatar: React.FC<{ avatarUrl?: string }> = ({ avatarUrl }) => 184 + const Avatar: React.FC<{ avatarUrl?: string; name?: string }> = ({ avatarUrl, name }) => 185 185 avatarUrl ? ( 186 - <img src={avatarUrl} alt="avatar" style={baseStyles.avatarImg} /> 186 + <img src={avatarUrl} alt={`${name || 'User'}'s profile picture`} style={baseStyles.avatarImg} /> 187 187 ) : ( 188 - <div style={baseStyles.avatarPlaceholder} aria-hidden /> 188 + <div style={baseStyles.avatarPlaceholder} aria-hidden="true" /> 189 189 ); 190 190 191 191 const ReplyInfo: React.FC<{ ··· 278 278 279 279 const ThreadLayout: React.FC<LayoutProps> = (props) => ( 280 280 <div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}> 281 - <Avatar avatarUrl={props.avatarUrl} /> 281 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 282 282 <div style={{ flex: 1, minWidth: 0 }}> 283 283 <div 284 284 style={{ ··· 326 326 const DefaultLayout: React.FC<LayoutProps> = (props) => ( 327 327 <> 328 328 <header style={baseStyles.header}> 329 - <Avatar avatarUrl={props.avatarUrl} /> 329 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 330 330 <AuthorInfo 331 331 primaryName={props.primaryName} 332 332 authorDisplayName={props.authorDisplayName} ··· 563 563 <img src={url} alt={alt} style={imagesBase.img} /> 564 564 ) : ( 565 565 <div 566 + role={error ? "alert" : "status"} 566 567 style={{ 567 568 ...imagesBase.placeholder, 568 569 color: `var(--atproto-color-text-muted)`,
+19 -17
lib/renderers/BlueskyProfileRenderer.tsx
··· 24 24 25 25 if (error) 26 26 return ( 27 - <div style={{ padding: 8, color: "crimson" }}> 27 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 28 28 Failed to load profile. 29 29 </div> 30 30 ); 31 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 31 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 32 32 33 33 const profileUrl = `${blueskyAppBaseUrl}/profile/${did}`; 34 34 const rawWebsite = record.website?.trim(); ··· 45 45 <div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 46 46 <div style={base.header}> 47 47 {avatarUrl ? ( 48 - <img src={avatarUrl} alt="avatar" style={base.avatarImg} /> 48 + <img src={avatarUrl} alt={`${record.displayName || handle || did}'s profile picture`} style={base.avatarImg} /> 49 49 ) : ( 50 50 <div 51 51 style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }} 52 - aria-label="avatar" 52 + aria-hidden="true" 53 53 /> 54 54 )} 55 55 <div style={{ flex: 1 }}> ··· 71 71 {record.description} 72 72 </p> 73 73 )} 74 - {websiteHref && websiteLabel && ( 75 - <div style={{ marginTop: 12 }}> 76 - <a 77 - href={websiteHref} 78 - target="_blank" 79 - rel="noopener noreferrer" 80 - style={{ ...base.link, color: `var(--atproto-color-link)` }} 81 - > 82 - {websiteLabel} 83 - </a> 84 - </div> 85 - )} 86 74 <div style={base.bottomRow}> 87 75 <div style={base.bottomLeft}> 88 76 {record.createdAt && ( 89 77 <div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}> 90 78 Joined {new Date(record.createdAt).toLocaleDateString()} 91 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> 92 90 )} 93 91 <a 94 92 href={profileUrl} ··· 109 107 110 108 const base: Record<string, React.CSSProperties> = { 111 109 card: { 110 + display: "flex", 111 + flexDirection: "column", 112 + height: "100%", 112 113 borderRadius: 12, 113 114 padding: 16, 114 115 fontFamily: "system-ui, sans-serif", ··· 171 172 display: "flex", 172 173 alignItems: "flex-end", 173 174 justifyContent: "space-between", 174 - marginTop: 12, 175 + marginTop: "auto", 176 + paddingTop: 12, 175 177 }, 176 178 bottomLeft: { 177 179 display: "flex",
+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;
+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;
+1 -3
lib/renderers/TangledStringRenderer.tsx
··· 1 1 import React from "react"; 2 - import type { ShTangledString } from "@atcute/tangled"; 3 2 import { useAtProto } from "../providers/AtProtoProvider"; 4 - 5 - export type TangledStringRecord = ShTangledString.Main; 3 + import type { TangledStringRecord } from "../types/tangled"; 6 4 7 5 export interface TangledStringRendererProps { 8 6 record: TangledStringRecord;
+59 -47
lib/styles.css
··· 7 7 8 8 :root { 9 9 /* Light theme colors (default) */ 10 - --atproto-color-bg: #ffffff; 11 - --atproto-color-bg-elevated: #f8fafc; 12 - --atproto-color-bg-secondary: #f1f5f9; 10 + --atproto-color-bg: #f5f7f9; 11 + --atproto-color-bg-elevated: #f8f9fb; 12 + --atproto-color-bg-secondary: #edf1f5; 13 13 --atproto-color-text: #0f172a; 14 14 --atproto-color-text-secondary: #475569; 15 15 --atproto-color-text-muted: #64748b; 16 - --atproto-color-border: #e2e8f0; 17 - --atproto-color-border-subtle: #cbd5e1; 16 + --atproto-color-border: #d6dce3; 17 + --atproto-color-border-subtle: #c1cad4; 18 + --atproto-color-border-hover: #94a3b8; 18 19 --atproto-color-link: #2563eb; 19 20 --atproto-color-link-hover: #1d4ed8; 20 21 --atproto-color-error: #dc2626; 21 - --atproto-color-button-bg: #f1f5f9; 22 - --atproto-color-button-hover: #e2e8f0; 22 + --atproto-color-primary: #2563eb; 23 + --atproto-color-button-bg: #edf1f5; 24 + --atproto-color-button-hover: #e3e9ef; 23 25 --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; 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; 30 34 --atproto-color-highlight: #fef08a; 31 35 } 32 36 33 37 /* Dark theme - can be applied via [data-theme="dark"] or .dark class */ 34 38 [data-theme="dark"], 35 39 .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; 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; 44 49 --atproto-color-link: #60a5fa; 45 50 --atproto-color-link-hover: #93c5fd; 46 51 --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; 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; 56 64 --atproto-color-highlight: #854d0e; 57 65 } 58 66 ··· 60 68 @media (prefers-color-scheme: dark) { 61 69 :root:not([data-theme]), 62 70 :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-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; 71 80 --atproto-color-link: #60a5fa; 72 81 --atproto-color-link-hover: #93c5fd; 73 82 --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-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; 83 95 --atproto-color-highlight: #854d0e; 84 96 } 85 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 + }
+2
lib/utils/atproto-client.ts
··· 23 23 const DEFAULT_APPVIEW = "https://public.api.bsky.app"; 24 24 const DEFAULT_BLUESKY_APP = "https://bsky.app"; 25 25 const DEFAULT_TANGLED = "https://tangled.org"; 26 + const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue"; 26 27 27 28 const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; 28 29 const SUPPORTED_DID_METHODS = ["plc", "web"] as const; ··· 40 41 blueskyAppviewService: DEFAULT_APPVIEW, 41 42 blueskyAppBaseUrl: DEFAULT_BLUESKY_APP, 42 43 tangledBaseUrl: DEFAULT_TANGLED, 44 + constellationBaseUrl: DEFAULT_CONSTELLATION, 43 45 } as const; 44 46 45 47 export const SLINGSHOT_BASE_URL = DEFAULT_SLINGSHOT;
+13
lib/utils/cache.ts
··· 290 290 export class RecordCache { 291 291 private store = new Map<string, RecordCacheEntry>(); 292 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 + ]); 293 298 294 299 private key(did: string, collection: string, rkey: string): string { 295 300 return `${did}::${collection}::${rkey}`; 296 301 } 297 302 303 + private shouldCache(collection: string): boolean { 304 + return !this.noCacheCollections.has(collection); 305 + } 306 + 298 307 get<T = unknown>( 299 308 did?: string, 300 309 collection?: string, 301 310 rkey?: string, 302 311 ): T | undefined { 303 312 if (!did || !collection || !rkey) return undefined; 313 + // Don't return cached data for non-cacheable collections 314 + if (!this.shouldCache(collection)) return undefined; 304 315 return this.store.get(this.key(did, collection, rkey))?.record as 305 316 | T 306 317 | undefined; ··· 312 323 rkey: string, 313 324 record: T, 314 325 ): void { 326 + // Don't cache records for non-cacheable collections 327 + if (!this.shouldCache(collection)) return; 315 328 this.store.set(this.key(did, collection, rkey), { 316 329 record, 317 330 timestamp: Date.now(),
+1219 -1234
package-lock.json
··· 1 1 { 2 2 "name": "atproto-ui", 3 - "version": "0.5.2-beta", 3 + "version": "0.12", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "atproto-ui", 9 - "version": "0.5.2-beta", 9 + "version": "0.12", 10 10 "dependencies": { 11 11 "@atcute/atproto": "^3.1.7", 12 12 "@atcute/bluesky": "^3.2.3", 13 13 "@atcute/client": "^4.0.3", 14 14 "@atcute/identity-resolver": "^1.1.3", 15 - "@atcute/tangled": "^1.0.6" 15 + "@atcute/tangled": "^1.0.10" 16 16 }, 17 17 "devDependencies": { 18 18 "@eslint/js": "^9.36.0", ··· 44 44 } 45 45 }, 46 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==", 47 + "version": "3.1.9", 50 48 "license": "0BSD", 51 49 "dependencies": { 52 50 "@atcute/lexicons": "^1.2.2" 53 51 } 54 52 }, 55 53 "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==", 54 + "version": "3.2.11", 59 55 "license": "0BSD", 60 56 "dependencies": { 61 - "@atcute/atproto": "^3.1.4", 62 - "@atcute/lexicons": "^1.1.1" 57 + "@atcute/atproto": "^3.1.9", 58 + "@atcute/lexicons": "^1.2.5" 63 59 } 64 60 }, 65 61 "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", 62 + "version": "4.1.0", 63 + "license": "0BSD", 70 64 "dependencies": { 71 - "@atcute/identity": "^1.0.2", 72 - "@atcute/lexicons": "^1.0.3" 65 + "@atcute/identity": "^1.1.3", 66 + "@atcute/lexicons": "^1.2.5" 73 67 } 74 68 }, 75 69 "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==", 70 + "version": "1.1.3", 79 71 "license": "0BSD", 72 + "peer": true, 80 73 "dependencies": { 81 - "@atcute/lexicons": "^1.1.1", 82 - "@badrap/valita": "^0.4.5" 74 + "@atcute/lexicons": "^1.2.4", 75 + "@badrap/valita": "^0.4.6" 83 76 } 84 77 }, 85 78 "node_modules/@atcute/identity-resolver": { 86 79 "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 80 "license": "0BSD", 90 81 "dependencies": { 91 82 "@atcute/lexicons": "^1.2.2", ··· 97 88 } 98 89 }, 99 90 "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==", 91 + "version": "1.2.5", 103 92 "license": "0BSD", 104 93 "dependencies": { 105 94 "@standard-schema/spec": "^1.0.0", ··· 107 96 } 108 97 }, 109 98 "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==", 99 + "version": "1.0.12", 113 100 "license": "0BSD", 114 101 "dependencies": { 115 - "@atcute/atproto": "^3.1.4", 116 - "@atcute/lexicons": "^1.1.1" 102 + "@atcute/atproto": "^3.1.9", 103 + "@atcute/lexicons": "^1.2.3" 117 104 } 118 105 }, 119 106 "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==", 107 + "version": "1.0.4", 123 108 "license": "0BSD", 124 109 "dependencies": { 125 110 "@badrap/valita": "^0.4.6" ··· 127 112 }, 128 113 "node_modules/@babel/code-frame": { 129 114 "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 115 "dev": true, 133 116 "license": "MIT", 134 117 "dependencies": { ··· 141 124 } 142 125 }, 143 126 "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==", 127 + "version": "7.28.5", 147 128 "dev": true, 148 129 "license": "MIT", 149 130 "engines": { ··· 151 132 } 152 133 }, 153 134 "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==", 135 + "version": "7.28.5", 157 136 "dev": true, 158 137 "license": "MIT", 138 + "peer": true, 159 139 "dependencies": { 160 140 "@babel/code-frame": "^7.27.1", 161 - "@babel/generator": "^7.28.3", 141 + "@babel/generator": "^7.28.5", 162 142 "@babel/helper-compilation-targets": "^7.27.2", 163 143 "@babel/helper-module-transforms": "^7.28.3", 164 144 "@babel/helpers": "^7.28.4", 165 - "@babel/parser": "^7.28.4", 145 + "@babel/parser": "^7.28.5", 166 146 "@babel/template": "^7.27.2", 167 - "@babel/traverse": "^7.28.4", 168 - "@babel/types": "^7.28.4", 147 + "@babel/traverse": "^7.28.5", 148 + "@babel/types": "^7.28.5", 169 149 "@jridgewell/remapping": "^2.3.5", 170 150 "convert-source-map": "^2.0.0", 171 151 "debug": "^4.1.0", ··· 181 161 "url": "https://opencollective.com/babel" 182 162 } 183 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 + }, 184 172 "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==", 173 + "version": "7.28.5", 188 174 "dev": true, 189 175 "license": "MIT", 190 176 "dependencies": { 191 - "@babel/parser": "^7.28.3", 192 - "@babel/types": "^7.28.2", 177 + "@babel/parser": "^7.28.5", 178 + "@babel/types": "^7.28.5", 193 179 "@jridgewell/gen-mapping": "^0.3.12", 194 180 "@jridgewell/trace-mapping": "^0.3.28", 195 181 "jsesc": "^3.0.2" ··· 200 186 }, 201 187 "node_modules/@babel/helper-compilation-targets": { 202 188 "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 189 "dev": true, 206 190 "license": "MIT", 207 191 "dependencies": { ··· 215 199 "node": ">=6.9.0" 216 200 } 217 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 + }, 218 223 "node_modules/@babel/helper-globals": { 219 224 "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 225 "dev": true, 223 226 "license": "MIT", 224 227 "engines": { ··· 227 230 }, 228 231 "node_modules/@babel/helper-module-imports": { 229 232 "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 233 "dev": true, 233 234 "license": "MIT", 234 235 "dependencies": { ··· 241 242 }, 242 243 "node_modules/@babel/helper-module-transforms": { 243 244 "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 245 "dev": true, 247 246 "license": "MIT", 248 247 "dependencies": { ··· 259 258 }, 260 259 "node_modules/@babel/helper-plugin-utils": { 261 260 "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 261 "dev": true, 265 262 "license": "MIT", 266 263 "engines": { ··· 269 266 }, 270 267 "node_modules/@babel/helper-string-parser": { 271 268 "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 269 "dev": true, 275 270 "license": "MIT", 276 271 "engines": { ··· 278 273 } 279 274 }, 280 275 "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==", 276 + "version": "7.28.5", 284 277 "dev": true, 285 278 "license": "MIT", 286 279 "engines": { ··· 289 282 }, 290 283 "node_modules/@babel/helper-validator-option": { 291 284 "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 285 "dev": true, 295 286 "license": "MIT", 296 287 "engines": { ··· 299 290 }, 300 291 "node_modules/@babel/helpers": { 301 292 "version": "7.28.4", 302 - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", 303 - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", 304 293 "dev": true, 305 294 "license": "MIT", 306 295 "dependencies": { ··· 312 301 } 313 302 }, 314 303 "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==", 304 + "version": "7.28.5", 318 305 "dev": true, 319 306 "license": "MIT", 320 307 "dependencies": { 321 - "@babel/types": "^7.28.4" 308 + "@babel/types": "^7.28.5" 322 309 }, 323 310 "bin": { 324 311 "parser": "bin/babel-parser.js" ··· 329 316 }, 330 317 "node_modules/@babel/plugin-transform-react-jsx-self": { 331 318 "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 319 "dev": true, 335 320 "license": "MIT", 336 321 "dependencies": { ··· 345 330 }, 346 331 "node_modules/@babel/plugin-transform-react-jsx-source": { 347 332 "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 333 "dev": true, 351 334 "license": "MIT", 352 335 "dependencies": { ··· 361 344 }, 362 345 "node_modules/@babel/template": { 363 346 "version": "7.27.2", 364 - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 365 - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 366 347 "dev": true, 367 348 "license": "MIT", 368 349 "dependencies": { ··· 375 356 } 376 357 }, 377 358 "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==", 359 + "version": "7.28.5", 381 360 "dev": true, 382 361 "license": "MIT", 383 362 "dependencies": { 384 363 "@babel/code-frame": "^7.27.1", 385 - "@babel/generator": "^7.28.3", 364 + "@babel/generator": "^7.28.5", 386 365 "@babel/helper-globals": "^7.28.0", 387 - "@babel/parser": "^7.28.4", 366 + "@babel/parser": "^7.28.5", 388 367 "@babel/template": "^7.27.2", 389 - "@babel/types": "^7.28.4", 368 + "@babel/types": "^7.28.5", 390 369 "debug": "^4.3.1" 391 370 }, 392 371 "engines": { ··· 394 373 } 395 374 }, 396 375 "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==", 376 + "version": "7.28.5", 400 377 "dev": true, 401 378 "license": "MIT", 402 379 "dependencies": { 403 380 "@babel/helper-string-parser": "^7.27.1", 404 - "@babel/helper-validator-identifier": "^7.27.1" 381 + "@babel/helper-validator-identifier": "^7.28.5" 405 382 }, 406 383 "engines": { 407 384 "node": ">=6.9.0" ··· 409 386 }, 410 387 "node_modules/@badrap/valita": { 411 388 "version": "0.4.6", 412 - "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz", 413 - "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==", 414 389 "license": "MIT", 415 390 "engines": { 416 391 "node": ">= 18" 417 392 } 418 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 + }, 419 428 "node_modules/@eslint-community/eslint-utils": { 420 429 "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 430 "dev": true, 424 431 "license": "MIT", 425 432 "dependencies": { ··· 437 444 }, 438 445 "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 439 446 "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 447 "dev": true, 443 448 "license": "Apache-2.0", 444 449 "engines": { ··· 449 454 } 450 455 }, 451 456 "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==", 457 + "version": "4.12.2", 455 458 "dev": true, 456 459 "license": "MIT", 457 460 "engines": { ··· 459 462 } 460 463 }, 461 464 "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 + "version": "0.21.1", 465 466 "dev": true, 466 467 "license": "Apache-2.0", 467 468 "dependencies": { 468 - "@eslint/object-schema": "^2.1.6", 469 + "@eslint/object-schema": "^2.1.7", 469 470 "debug": "^4.3.1", 470 471 "minimatch": "^3.1.2" 471 472 }, ··· 473 474 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 474 475 } 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 + }, 476 488 "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==", 489 + "version": "0.4.2", 480 490 "dev": true, 481 491 "license": "Apache-2.0", 482 492 "dependencies": { 483 - "@eslint/core": "^0.16.0" 493 + "@eslint/core": "^0.17.0" 484 494 }, 485 495 "engines": { 486 496 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 487 497 } 488 498 }, 489 499 "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==", 500 + "version": "0.17.0", 493 501 "dev": true, 494 502 "license": "Apache-2.0", 495 503 "dependencies": { ··· 500 508 } 501 509 }, 502 510 "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==", 511 + "version": "3.3.3", 506 512 "dev": true, 507 513 "license": "MIT", 508 514 "dependencies": { ··· 512 518 "globals": "^14.0.0", 513 519 "ignore": "^5.2.0", 514 520 "import-fresh": "^3.2.1", 515 - "js-yaml": "^4.1.0", 521 + "js-yaml": "^4.1.1", 516 522 "minimatch": "^3.1.2", 517 523 "strip-json-comments": "^3.1.1" 518 524 }, ··· 523 529 "url": "https://opencollective.com/eslint" 524 530 } 525 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 + }, 526 549 "node_modules/@eslint/eslintrc/node_modules/globals": { 527 550 "version": "14.0.0", 528 - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 529 - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 530 551 "dev": true, 531 552 "license": "MIT", 532 553 "engines": { ··· 536 557 "url": "https://github.com/sponsors/sindresorhus" 537 558 } 538 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 + }, 539 578 "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==", 579 + "version": "9.39.1", 543 580 "dev": true, 544 581 "license": "MIT", 545 582 "engines": { ··· 550 587 } 551 588 }, 552 589 "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==", 590 + "version": "2.1.7", 556 591 "dev": true, 557 592 "license": "Apache-2.0", 558 593 "engines": { ··· 560 595 } 561 596 }, 562 597 "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==", 598 + "version": "0.4.1", 566 599 "dev": true, 567 600 "license": "Apache-2.0", 568 601 "dependencies": { 569 - "@eslint/core": "^0.16.0", 602 + "@eslint/core": "^0.17.0", 570 603 "levn": "^0.4.1" 571 604 }, 572 605 "engines": { ··· 575 608 }, 576 609 "node_modules/@humanfs/core": { 577 610 "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 611 "dev": true, 581 612 "license": "Apache-2.0", 582 613 "engines": { ··· 585 616 }, 586 617 "node_modules/@humanfs/node": { 587 618 "version": "0.16.7", 588 - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 589 - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 590 619 "dev": true, 591 620 "license": "Apache-2.0", 592 621 "dependencies": { ··· 599 628 }, 600 629 "node_modules/@humanwhocodes/module-importer": { 601 630 "version": "1.0.1", 602 - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 603 - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 604 631 "dev": true, 605 632 "license": "Apache-2.0", 606 633 "engines": { ··· 613 640 }, 614 641 "node_modules/@humanwhocodes/retry": { 615 642 "version": "0.4.3", 616 - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 617 - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 618 643 "dev": true, 619 644 "license": "Apache-2.0", 620 645 "engines": { ··· 627 652 }, 628 653 "node_modules/@isaacs/balanced-match": { 629 654 "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 655 "dev": true, 633 656 "license": "MIT", 634 657 "engines": { ··· 637 660 }, 638 661 "node_modules/@isaacs/brace-expansion": { 639 662 "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 663 "dev": true, 643 664 "license": "MIT", 644 665 "dependencies": { ··· 650 671 }, 651 672 "node_modules/@jridgewell/gen-mapping": { 652 673 "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 674 "dev": true, 656 675 "license": "MIT", 657 676 "dependencies": { ··· 661 680 }, 662 681 "node_modules/@jridgewell/remapping": { 663 682 "version": "2.3.5", 664 - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 665 - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 666 683 "dev": true, 667 684 "license": "MIT", 668 685 "dependencies": { ··· 672 689 }, 673 690 "node_modules/@jridgewell/resolve-uri": { 674 691 "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 692 "dev": true, 678 693 "license": "MIT", 679 694 "engines": { ··· 682 697 }, 683 698 "node_modules/@jridgewell/source-map": { 684 699 "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 700 "dev": true, 688 701 "license": "MIT", 689 702 "optional": true, 690 - "peer": true, 691 703 "dependencies": { 692 704 "@jridgewell/gen-mapping": "^0.3.5", 693 705 "@jridgewell/trace-mapping": "^0.3.25" ··· 695 707 }, 696 708 "node_modules/@jridgewell/sourcemap-codec": { 697 709 "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 710 "dev": true, 701 711 "license": "MIT" 702 712 }, 703 713 "node_modules/@jridgewell/trace-mapping": { 704 714 "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 715 "dev": true, 708 716 "license": "MIT", 709 717 "dependencies": { ··· 712 720 } 713 721 }, 714 722 "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==", 723 + "version": "7.55.1", 718 724 "dev": true, 719 725 "license": "MIT", 720 726 "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", 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", 725 731 "@rushstack/rig-package": "0.6.0", 726 - "@rushstack/terminal": "0.19.1", 727 - "@rushstack/ts-command-line": "5.1.1", 732 + "@rushstack/terminal": "0.19.4", 733 + "@rushstack/ts-command-line": "5.1.4", 734 + "diff": "~8.0.2", 728 735 "lodash": "~4.17.15", 729 736 "minimatch": "10.0.3", 730 737 "resolve": "~1.22.1", ··· 737 744 } 738 745 }, 739 746 "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==", 747 + "version": "7.32.1", 743 748 "dev": true, 744 749 "license": "MIT", 745 750 "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" 751 + "@microsoft/tsdoc": "~0.16.0", 752 + "@microsoft/tsdoc-config": "~0.18.0", 753 + "@rushstack/node-core-library": "5.19.0" 794 754 } 795 755 }, 796 756 "node_modules/@microsoft/api-extractor/node_modules/typescript": { 797 757 "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 758 "dev": true, 801 759 "license": "Apache-2.0", 802 760 "bin": { ··· 807 765 "node": ">=14.17" 808 766 } 809 767 }, 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 768 "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==", 769 + "version": "0.16.0", 821 770 "dev": true, 822 771 "license": "MIT" 823 772 }, 824 773 "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==", 774 + "version": "0.18.0", 828 775 "dev": true, 829 776 "license": "MIT", 830 777 "dependencies": { 831 - "@microsoft/tsdoc": "0.15.1", 778 + "@microsoft/tsdoc": "0.16.0", 832 779 "ajv": "~8.12.0", 833 780 "jju": "~1.4.0", 834 781 "resolve": "~1.22.2" ··· 836 783 }, 837 784 "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { 838 785 "version": "8.12.0", 839 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", 840 - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", 841 786 "dev": true, 842 787 "license": "MIT", 843 788 "dependencies": { ··· 851 796 "url": "https://github.com/sponsors/epoberezkin" 852 797 } 853 798 }, 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==", 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 + ], 858 937 "dev": true, 859 - "license": "MIT" 938 + "license": "MIT", 939 + "optional": true, 940 + "os": [ 941 + "linux" 942 + ], 943 + "engines": { 944 + "node": "^20.19.0 || >=22.12.0" 945 + } 860 946 }, 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==", 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 + ], 865 954 "dev": true, 866 955 "license": "MIT", 867 - "dependencies": { 868 - "@nodelib/fs.stat": "2.0.5", 869 - "run-parallel": "^1.1.9" 870 - }, 956 + "optional": true, 957 + "os": [ 958 + "linux" 959 + ], 871 960 "engines": { 872 - "node": ">= 8" 961 + "node": "^20.19.0 || >=22.12.0" 873 962 } 874 963 }, 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==", 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 + ], 879 971 "dev": true, 880 972 "license": "MIT", 973 + "optional": true, 974 + "os": [ 975 + "linux" 976 + ], 881 977 "engines": { 882 - "node": ">= 8" 978 + "node": "^20.19.0 || >=22.12.0" 883 979 } 884 980 }, 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==", 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 + ], 889 1005 "dev": true, 890 1006 "license": "MIT", 1007 + "optional": true, 891 1008 "dependencies": { 892 - "@nodelib/fs.scandir": "2.1.5", 893 - "fastq": "^1.6.0" 1009 + "@napi-rs/wasm-runtime": "^1.0.5" 894 1010 }, 895 1011 "engines": { 896 - "node": ">= 8" 1012 + "node": ">=14.0.0" 897 1013 } 898 1014 }, 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==", 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 + ], 903 1022 "dev": true, 904 1023 "license": "MIT", 1024 + "optional": true, 1025 + "os": [ 1026 + "win32" 1027 + ], 905 1028 "engines": { 906 1029 "node": "^20.19.0 || >=22.12.0" 907 1030 } 908 1031 }, 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==", 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 + ], 913 1039 "dev": true, 914 1040 "license": "MIT", 915 - "funding": { 916 - "url": "https://github.com/sponsors/Boshen" 1041 + "optional": true, 1042 + "os": [ 1043 + "win32" 1044 + ], 1045 + "engines": { 1046 + "node": "^20.19.0 || >=22.12.0" 917 1047 } 918 1048 }, 919 - "node_modules/@rolldown/binding-darwin-arm64": { 1049 + "node_modules/@rolldown/binding-win32-x64-msvc": { 920 1050 "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 1051 "cpu": [ 924 - "arm64" 1052 + "x64" 925 1053 ], 926 1054 "dev": true, 927 1055 "license": "MIT", 928 1056 "optional": true, 929 1057 "os": [ 930 - "darwin" 1058 + "win32" 931 1059 ], 932 1060 "engines": { 933 1061 "node": "^20.19.0 || >=22.12.0" 934 1062 } 935 1063 }, 936 1064 "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==", 1065 + "version": "1.0.0-beta.47", 940 1066 "dev": true, 941 1067 "license": "MIT" 942 1068 }, 943 1069 "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==", 1070 + "version": "4.2.1", 947 1071 "dev": true, 948 1072 "license": "MIT", 949 1073 "dependencies": { 950 - "@types/estree": "^1.0.0", 951 - "estree-walker": "^2.0.2", 952 - "picomatch": "^4.0.2" 1074 + "estree-walker": "^2.0.1", 1075 + "picomatch": "^2.2.2" 953 1076 }, 954 1077 "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 - } 1078 + "node": ">= 8.0.0" 964 1079 } 965 1080 }, 966 1081 "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==", 1082 + "version": "2.3.1", 970 1083 "dev": true, 971 1084 "license": "MIT", 972 1085 "engines": { 973 - "node": ">=12" 1086 + "node": ">=8.6" 974 1087 }, 975 1088 "funding": { 976 1089 "url": "https://github.com/sponsors/jonschlinkert" 977 1090 } 978 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 + }, 979 1120 "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==", 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==", 983 1124 "cpu": [ 984 1125 "arm64" 985 1126 ], ··· 988 1129 "optional": true, 989 1130 "os": [ 990 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" 991 1140 ], 992 - "peer": true 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 + ] 993 1395 }, 994 1396 "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==", 1397 + "version": "5.19.0", 998 1398 "dev": true, 999 1399 "license": "MIT", 1000 1400 "dependencies": { ··· 1018 1418 }, 1019 1419 "node_modules/@rushstack/node-core-library/node_modules/ajv": { 1020 1420 "version": "8.13.0", 1021 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", 1022 - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", 1023 1421 "dev": true, 1024 1422 "license": "MIT", 1025 1423 "dependencies": { ··· 1033 1431 "url": "https://github.com/sponsors/epoberezkin" 1034 1432 } 1035 1433 }, 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==", 1434 + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { 1435 + "version": "11.3.2", 1040 1436 "dev": true, 1041 1437 "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 1438 "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" 1439 + "graceful-fs": "^4.2.0", 1440 + "jsonfile": "^6.0.1", 1441 + "universalify": "^2.0.0" 1082 1442 }, 1083 1443 "engines": { 1084 - "node": ">=10" 1444 + "node": ">=14.14" 1085 1445 } 1086 1446 }, 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 1447 "node_modules/@rushstack/problem-matcher": { 1095 1448 "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 1449 "dev": true, 1099 1450 "license": "MIT", 1100 1451 "peerDependencies": { ··· 1108 1459 }, 1109 1460 "node_modules/@rushstack/rig-package": { 1110 1461 "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 1462 "dev": true, 1114 1463 "license": "MIT", 1115 1464 "dependencies": { ··· 1118 1467 } 1119 1468 }, 1120 1469 "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==", 1470 + "version": "0.19.4", 1124 1471 "dev": true, 1125 1472 "license": "MIT", 1126 1473 "dependencies": { 1127 - "@rushstack/node-core-library": "5.17.0", 1474 + "@rushstack/node-core-library": "5.19.0", 1128 1475 "@rushstack/problem-matcher": "0.1.1", 1129 1476 "supports-color": "~8.1.1" 1130 1477 }, ··· 1137 1484 } 1138 1485 } 1139 1486 }, 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 1487 "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==", 1488 + "version": "5.1.4", 1160 1489 "dev": true, 1161 1490 "license": "MIT", 1162 1491 "dependencies": { 1163 - "@rushstack/terminal": "0.19.1", 1492 + "@rushstack/terminal": "0.19.4", 1164 1493 "@types/argparse": "1.0.38", 1165 1494 "argparse": "~1.0.9", 1166 1495 "string-argv": "~0.3.1" 1167 1496 } 1168 1497 }, 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==", 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==", 1173 1506 "dev": true, 1174 1507 "license": "MIT", 1508 + "optional": true, 1175 1509 "dependencies": { 1176 - "sprintf-js": "~1.0.2" 1510 + "tslib": "^2.4.0" 1177 1511 } 1178 1512 }, 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 1513 "node_modules/@types/argparse": { 1186 1514 "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 1515 "dev": true, 1190 1516 "license": "MIT" 1191 1517 }, 1192 1518 "node_modules/@types/babel__core": { 1193 1519 "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 1520 "dev": true, 1197 1521 "license": "MIT", 1198 1522 "dependencies": { ··· 1205 1529 }, 1206 1530 "node_modules/@types/babel__generator": { 1207 1531 "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 1532 "dev": true, 1211 1533 "license": "MIT", 1212 1534 "dependencies": { ··· 1215 1537 }, 1216 1538 "node_modules/@types/babel__template": { 1217 1539 "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 1540 "dev": true, 1221 1541 "license": "MIT", 1222 1542 "dependencies": { ··· 1226 1546 }, 1227 1547 "node_modules/@types/babel__traverse": { 1228 1548 "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 1549 "dev": true, 1232 1550 "license": "MIT", 1233 1551 "dependencies": { ··· 1236 1554 }, 1237 1555 "node_modules/@types/estree": { 1238 1556 "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 1557 "dev": true, 1242 1558 "license": "MIT" 1243 1559 }, 1244 1560 "node_modules/@types/json-schema": { 1245 1561 "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 1562 "dev": true, 1249 1563 "license": "MIT" 1250 1564 }, 1251 1565 "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==", 1566 + "version": "24.10.1", 1255 1567 "dev": true, 1256 1568 "license": "MIT", 1569 + "peer": true, 1257 1570 "dependencies": { 1258 - "undici-types": "~7.14.0" 1571 + "undici-types": "~7.16.0" 1259 1572 } 1260 1573 }, 1261 1574 "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==", 1575 + "version": "19.2.7", 1265 1576 "dev": true, 1266 1577 "license": "MIT", 1578 + "peer": true, 1267 1579 "dependencies": { 1268 - "csstype": "^3.0.2" 1580 + "csstype": "^3.2.2" 1269 1581 } 1270 1582 }, 1271 1583 "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==", 1584 + "version": "19.2.3", 1275 1585 "dev": true, 1276 1586 "license": "MIT", 1277 1587 "peerDependencies": { ··· 1279 1589 } 1280 1590 }, 1281 1591 "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==", 1592 + "version": "8.48.1", 1285 1593 "dev": true, 1286 1594 "license": "MIT", 1287 1595 "dependencies": { 1288 1596 "@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", 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", 1293 1601 "graphemer": "^1.4.0", 1294 1602 "ignore": "^7.0.0", 1295 1603 "natural-compare": "^1.4.0", ··· 1303 1611 "url": "https://opencollective.com/typescript-eslint" 1304 1612 }, 1305 1613 "peerDependencies": { 1306 - "@typescript-eslint/parser": "^8.46.0", 1614 + "@typescript-eslint/parser": "^8.48.1", 1307 1615 "eslint": "^8.57.0 || ^9.0.0", 1308 1616 "typescript": ">=4.8.4 <6.0.0" 1309 1617 } 1310 1618 }, 1311 1619 "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1312 1620 "version": "7.0.5", 1313 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1314 - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1315 1621 "dev": true, 1316 1622 "license": "MIT", 1317 1623 "engines": { ··· 1319 1625 } 1320 1626 }, 1321 1627 "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==", 1628 + "version": "8.48.1", 1325 1629 "dev": true, 1326 1630 "license": "MIT", 1631 + "peer": true, 1327 1632 "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", 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", 1332 1637 "debug": "^4.3.4" 1333 1638 }, 1334 1639 "engines": { ··· 1344 1649 } 1345 1650 }, 1346 1651 "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==", 1652 + "version": "8.48.1", 1350 1653 "dev": true, 1351 1654 "license": "MIT", 1352 1655 "dependencies": { 1353 - "@typescript-eslint/tsconfig-utils": "^8.46.0", 1354 - "@typescript-eslint/types": "^8.46.0", 1656 + "@typescript-eslint/tsconfig-utils": "^8.48.1", 1657 + "@typescript-eslint/types": "^8.48.1", 1355 1658 "debug": "^4.3.4" 1356 1659 }, 1357 1660 "engines": { ··· 1366 1669 } 1367 1670 }, 1368 1671 "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==", 1672 + "version": "8.48.1", 1372 1673 "dev": true, 1373 1674 "license": "MIT", 1374 1675 "dependencies": { 1375 - "@typescript-eslint/types": "8.46.0", 1376 - "@typescript-eslint/visitor-keys": "8.46.0" 1676 + "@typescript-eslint/types": "8.48.1", 1677 + "@typescript-eslint/visitor-keys": "8.48.1" 1377 1678 }, 1378 1679 "engines": { 1379 1680 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1384 1685 } 1385 1686 }, 1386 1687 "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==", 1688 + "version": "8.48.1", 1390 1689 "dev": true, 1391 1690 "license": "MIT", 1392 1691 "engines": { ··· 1401 1700 } 1402 1701 }, 1403 1702 "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==", 1703 + "version": "8.48.1", 1407 1704 "dev": true, 1408 1705 "license": "MIT", 1409 1706 "dependencies": { 1410 - "@typescript-eslint/types": "8.46.0", 1411 - "@typescript-eslint/typescript-estree": "8.46.0", 1412 - "@typescript-eslint/utils": "8.46.0", 1707 + "@typescript-eslint/types": "8.48.1", 1708 + "@typescript-eslint/typescript-estree": "8.48.1", 1709 + "@typescript-eslint/utils": "8.48.1", 1413 1710 "debug": "^4.3.4", 1414 1711 "ts-api-utils": "^2.1.0" 1415 1712 }, ··· 1426 1723 } 1427 1724 }, 1428 1725 "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==", 1726 + "version": "8.48.1", 1432 1727 "dev": true, 1433 1728 "license": "MIT", 1434 1729 "engines": { ··· 1440 1735 } 1441 1736 }, 1442 1737 "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==", 1738 + "version": "8.48.1", 1446 1739 "dev": true, 1447 1740 "license": "MIT", 1448 1741 "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", 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", 1453 1746 "debug": "^4.3.4", 1454 - "fast-glob": "^3.3.2", 1455 - "is-glob": "^4.0.3", 1456 1747 "minimatch": "^9.0.4", 1457 1748 "semver": "^7.6.0", 1749 + "tinyglobby": "^0.2.15", 1458 1750 "ts-api-utils": "^2.1.0" 1459 1751 }, 1460 1752 "engines": { ··· 1468 1760 "typescript": ">=4.8.4 <6.0.0" 1469 1761 } 1470 1762 }, 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 1763 "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1482 1764 "version": "9.0.5", 1483 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1484 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1485 1765 "dev": true, 1486 1766 "license": "ISC", 1487 1767 "dependencies": { ··· 1494 1774 "url": "https://github.com/sponsors/isaacs" 1495 1775 } 1496 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 + }, 1497 1785 "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1498 1786 "version": "7.7.3", 1499 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 1500 - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 1501 1787 "dev": true, 1502 1788 "license": "ISC", 1503 1789 "bin": { ··· 1508 1794 } 1509 1795 }, 1510 1796 "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==", 1797 + "version": "8.48.1", 1514 1798 "dev": true, 1515 1799 "license": "MIT", 1516 1800 "dependencies": { 1517 1801 "@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" 1802 + "@typescript-eslint/scope-manager": "8.48.1", 1803 + "@typescript-eslint/types": "8.48.1", 1804 + "@typescript-eslint/typescript-estree": "8.48.1" 1521 1805 }, 1522 1806 "engines": { 1523 1807 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 1532 1816 } 1533 1817 }, 1534 1818 "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==", 1819 + "version": "8.48.1", 1538 1820 "dev": true, 1539 1821 "license": "MIT", 1540 1822 "dependencies": { 1541 - "@typescript-eslint/types": "8.46.0", 1823 + "@typescript-eslint/types": "8.48.1", 1542 1824 "eslint-visitor-keys": "^4.2.1" 1543 1825 }, 1544 1826 "engines": { ··· 1550 1832 } 1551 1833 }, 1552 1834 "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==", 1835 + "version": "5.1.1", 1556 1836 "dev": true, 1557 1837 "license": "MIT", 1558 1838 "dependencies": { 1559 - "@babel/core": "^7.28.4", 1839 + "@babel/core": "^7.28.5", 1560 1840 "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1561 1841 "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1562 - "@rolldown/pluginutils": "1.0.0-beta.38", 1842 + "@rolldown/pluginutils": "1.0.0-beta.47", 1563 1843 "@types/babel__core": "^7.20.5", 1564 - "react-refresh": "^0.17.0" 1844 + "react-refresh": "^0.18.0" 1565 1845 }, 1566 1846 "engines": { 1567 1847 "node": "^20.19.0 || >=22.12.0" ··· 1571 1851 } 1572 1852 }, 1573 1853 "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==", 1854 + "version": "2.4.26", 1577 1855 "dev": true, 1578 1856 "license": "MIT", 1579 1857 "dependencies": { 1580 - "@volar/source-map": "2.4.23" 1858 + "@volar/source-map": "2.4.26" 1581 1859 } 1582 1860 }, 1583 1861 "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==", 1862 + "version": "2.4.26", 1587 1863 "dev": true, 1588 1864 "license": "MIT" 1589 1865 }, 1590 1866 "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==", 1867 + "version": "2.4.26", 1594 1868 "dev": true, 1595 1869 "license": "MIT", 1596 1870 "dependencies": { 1597 - "@volar/language-core": "2.4.23", 1871 + "@volar/language-core": "2.4.26", 1598 1872 "path-browserify": "^1.0.1", 1599 1873 "vscode-uri": "^3.0.8" 1600 1874 } 1601 1875 }, 1602 1876 "node_modules/acorn": { 1603 1877 "version": "8.15.0", 1604 - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 1605 - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 1606 1878 "dev": true, 1607 1879 "license": "MIT", 1880 + "peer": true, 1608 1881 "bin": { 1609 1882 "acorn": "bin/acorn" 1610 1883 }, ··· 1614 1887 }, 1615 1888 "node_modules/acorn-jsx": { 1616 1889 "version": "5.3.2", 1617 - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1618 - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1619 1890 "dev": true, 1620 1891 "license": "MIT", 1621 1892 "peerDependencies": { ··· 1623 1894 } 1624 1895 }, 1625 1896 "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==", 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==", 1629 1900 "dev": true, 1630 1901 "license": "MIT", 1902 + "peer": true, 1631 1903 "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" 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" 1636 1908 }, 1637 1909 "funding": { 1638 1910 "type": "github", 1639 1911 "url": "https://github.com/sponsors/epoberezkin" 1640 1912 } 1641 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 + }, 1642 1927 "node_modules/ajv-formats": { 1643 1928 "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 1929 "dev": true, 1647 1930 "license": "MIT", 1648 1931 "dependencies": { ··· 1657 1940 } 1658 1941 } 1659 1942 }, 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 1943 "node_modules/ansi-styles": { 1685 1944 "version": "4.3.0", 1686 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1687 - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1688 1945 "dev": true, 1689 1946 "license": "MIT", 1690 1947 "dependencies": { ··· 1699 1956 }, 1700 1957 "node_modules/ansis": { 1701 1958 "version": "4.2.0", 1702 - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", 1703 - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", 1704 1959 "dev": true, 1705 1960 "license": "ISC", 1706 1961 "engines": { ··· 1708 1963 } 1709 1964 }, 1710 1965 "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==", 1966 + "version": "1.0.10", 1714 1967 "dev": true, 1715 - "license": "Python-2.0" 1968 + "license": "MIT", 1969 + "dependencies": { 1970 + "sprintf-js": "~1.0.2" 1971 + } 1716 1972 }, 1717 1973 "node_modules/balanced-match": { 1718 1974 "version": "1.0.2", 1719 - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1720 - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1721 1975 "dev": true, 1722 1976 "license": "MIT" 1723 1977 }, 1724 1978 "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==", 1979 + "version": "2.8.32", 1728 1980 "dev": true, 1729 1981 "license": "Apache-2.0", 1730 1982 "bin": { ··· 1733 1985 }, 1734 1986 "node_modules/brace-expansion": { 1735 1987 "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 1988 "dev": true, 1739 1989 "license": "MIT", 1740 1990 "dependencies": { ··· 1742 1992 "concat-map": "0.0.1" 1743 1993 } 1744 1994 }, 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 1995 "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==", 1996 + "version": "4.28.0", 1762 1997 "dev": true, 1763 1998 "funding": [ 1764 1999 { ··· 1775 2010 } 1776 2011 ], 1777 2012 "license": "MIT", 2013 + "peer": true, 1778 2014 "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" 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" 1784 2020 }, 1785 2021 "bin": { 1786 2022 "browserslist": "cli.js" ··· 1791 2027 }, 1792 2028 "node_modules/buffer-from": { 1793 2029 "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 2030 "dev": true, 1797 2031 "license": "MIT", 1798 - "optional": true, 1799 - "peer": true 2032 + "optional": true 1800 2033 }, 1801 2034 "node_modules/callsites": { 1802 2035 "version": "3.1.0", 1803 - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1804 - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1805 2036 "dev": true, 1806 2037 "license": "MIT", 1807 2038 "engines": { ··· 1809 2040 } 1810 2041 }, 1811 2042 "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==", 2043 + "version": "1.0.30001759", 1815 2044 "dev": true, 1816 2045 "funding": [ 1817 2046 { ··· 1831 2060 }, 1832 2061 "node_modules/chalk": { 1833 2062 "version": "4.1.2", 1834 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1835 - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1836 2063 "dev": true, 1837 2064 "license": "MIT", 1838 2065 "dependencies": { ··· 1846 2073 "url": "https://github.com/chalk/chalk?sponsor=1" 1847 2074 } 1848 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 + }, 1849 2087 "node_modules/color-convert": { 1850 2088 "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 2089 "dev": true, 1854 2090 "license": "MIT", 1855 2091 "dependencies": { ··· 1861 2097 }, 1862 2098 "node_modules/color-name": { 1863 2099 "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 2100 "dev": true, 1867 2101 "license": "MIT" 1868 2102 }, 1869 2103 "node_modules/commander": { 1870 2104 "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 2105 "dev": true, 1874 2106 "license": "MIT", 1875 - "optional": true, 1876 - "peer": true 2107 + "optional": true 1877 2108 }, 1878 2109 "node_modules/commondir": { 1879 2110 "version": "1.0.1", 1880 - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 1881 - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", 1882 2111 "dev": true, 1883 2112 "license": "MIT" 1884 2113 }, 1885 2114 "node_modules/compare-versions": { 1886 2115 "version": "6.1.1", 1887 - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", 1888 - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", 1889 2116 "dev": true, 1890 2117 "license": "MIT" 1891 2118 }, 1892 2119 "node_modules/concat-map": { 1893 2120 "version": "0.0.1", 1894 - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1895 - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1896 2121 "dev": true, 1897 2122 "license": "MIT" 1898 2123 }, 1899 2124 "node_modules/confbox": { 1900 2125 "version": "0.2.2", 1901 - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", 1902 - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", 1903 2126 "dev": true, 1904 2127 "license": "MIT" 1905 2128 }, 1906 2129 "node_modules/convert-source-map": { 1907 2130 "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 2131 "dev": true, 1911 2132 "license": "MIT" 1912 2133 }, 1913 2134 "node_modules/cross-spawn": { 1914 2135 "version": "7.0.6", 1915 - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1916 - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1917 2136 "dev": true, 1918 2137 "license": "MIT", 1919 2138 "dependencies": { ··· 1926 2145 } 1927 2146 }, 1928 2147 "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==", 2148 + "version": "3.2.3", 1932 2149 "dev": true, 1933 2150 "license": "MIT" 1934 2151 }, 1935 2152 "node_modules/debug": { 1936 2153 "version": "4.4.3", 1937 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1938 - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1939 2154 "dev": true, 1940 2155 "license": "MIT", 1941 2156 "dependencies": { ··· 1952 2167 }, 1953 2168 "node_modules/deep-is": { 1954 2169 "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 2170 "dev": true, 1958 2171 "license": "MIT" 1959 2172 }, 1960 2173 "node_modules/detect-libc": { 1961 2174 "version": "2.1.2", 1962 - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1963 - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1964 2175 "dev": true, 1965 2176 "license": "Apache-2.0", 1966 2177 "engines": { 1967 2178 "node": ">=8" 1968 2179 } 1969 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 + }, 1970 2189 "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==", 2190 + "version": "1.5.263", 1974 2191 "dev": true, 1975 2192 "license": "ISC" 1976 2193 }, 1977 2194 "node_modules/escalade": { 1978 2195 "version": "3.2.0", 1979 - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 1980 - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 1981 2196 "dev": true, 1982 2197 "license": "MIT", 1983 2198 "engines": { ··· 1986 2201 }, 1987 2202 "node_modules/escape-string-regexp": { 1988 2203 "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 2204 "dev": true, 1992 2205 "license": "MIT", 1993 2206 "engines": { ··· 1998 2211 } 1999 2212 }, 2000 2213 "node_modules/eslint": { 2001 - "version": "9.37.0", 2002 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", 2003 - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", 2214 + "version": "9.39.1", 2004 2215 "dev": true, 2005 2216 "license": "MIT", 2217 + "peer": true, 2006 2218 "dependencies": { 2007 2219 "@eslint-community/eslint-utils": "^4.8.0", 2008 2220 "@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", 2221 + "@eslint/config-array": "^0.21.1", 2222 + "@eslint/config-helpers": "^0.4.2", 2223 + "@eslint/core": "^0.17.0", 2012 2224 "@eslint/eslintrc": "^3.3.1", 2013 - "@eslint/js": "9.37.0", 2014 - "@eslint/plugin-kit": "^0.4.0", 2225 + "@eslint/js": "9.39.1", 2226 + "@eslint/plugin-kit": "^0.4.1", 2015 2227 "@humanfs/node": "^0.16.6", 2016 2228 "@humanwhocodes/module-importer": "^1.0.1", 2017 2229 "@humanwhocodes/retry": "^0.4.2", 2018 2230 "@types/estree": "^1.0.6", 2019 - "@types/json-schema": "^7.0.15", 2020 2231 "ajv": "^6.12.4", 2021 2232 "chalk": "^4.0.0", 2022 2233 "cross-spawn": "^7.0.6", ··· 2060 2271 }, 2061 2272 "node_modules/eslint-plugin-react-hooks": { 2062 2273 "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 2274 "dev": true, 2066 2275 "license": "MIT", 2067 2276 "engines": { ··· 2072 2281 } 2073 2282 }, 2074 2283 "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==", 2284 + "version": "0.4.24", 2078 2285 "dev": true, 2079 2286 "license": "MIT", 2080 2287 "peerDependencies": { ··· 2083 2290 }, 2084 2291 "node_modules/eslint-scope": { 2085 2292 "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 2293 "dev": true, 2089 2294 "license": "BSD-2-Clause", 2090 2295 "dependencies": { ··· 2100 2305 }, 2101 2306 "node_modules/eslint-visitor-keys": { 2102 2307 "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 2308 "dev": true, 2106 2309 "license": "Apache-2.0", 2107 2310 "engines": { ··· 2111 2314 "url": "https://opencollective.com/eslint" 2112 2315 } 2113 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 + }, 2114 2352 "node_modules/esm-env": { 2115 2353 "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 2354 "license": "MIT" 2119 2355 }, 2120 2356 "node_modules/espree": { 2121 2357 "version": "10.4.0", 2122 - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 2123 - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 2124 2358 "dev": true, 2125 2359 "license": "BSD-2-Clause", 2126 2360 "dependencies": { ··· 2137 2371 }, 2138 2372 "node_modules/esquery": { 2139 2373 "version": "1.6.0", 2140 - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 2141 - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 2142 2374 "dev": true, 2143 2375 "license": "BSD-3-Clause", 2144 2376 "dependencies": { ··· 2150 2382 }, 2151 2383 "node_modules/esrecurse": { 2152 2384 "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 2385 "dev": true, 2156 2386 "license": "BSD-2-Clause", 2157 2387 "dependencies": { ··· 2163 2393 }, 2164 2394 "node_modules/estraverse": { 2165 2395 "version": "5.3.0", 2166 - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 2167 - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 2168 2396 "dev": true, 2169 2397 "license": "BSD-2-Clause", 2170 2398 "engines": { ··· 2173 2401 }, 2174 2402 "node_modules/estree-walker": { 2175 2403 "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 2404 "dev": true, 2179 2405 "license": "MIT" 2180 2406 }, 2181 2407 "node_modules/esutils": { 2182 2408 "version": "2.0.3", 2183 - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 2184 - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 2185 2409 "dev": true, 2186 2410 "license": "BSD-2-Clause", 2187 2411 "engines": { ··· 2189 2413 } 2190 2414 }, 2191 2415 "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==", 2416 + "version": "1.0.8", 2195 2417 "dev": true, 2196 2418 "license": "MIT" 2197 2419 }, 2198 2420 "node_modules/fast-deep-equal": { 2199 2421 "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 2422 "dev": true, 2203 2423 "license": "MIT" 2204 2424 }, 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 2425 "node_modules/fast-json-stable-stringify": { 2236 2426 "version": "2.1.0", 2237 2427 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", ··· 2241 2431 }, 2242 2432 "node_modules/fast-levenshtein": { 2243 2433 "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 2434 "dev": true, 2247 2435 "license": "MIT" 2248 2436 }, 2249 2437 "node_modules/fast-uri": { 2250 2438 "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 2439 "dev": true, 2254 2440 "funding": [ 2255 2441 { ··· 2263 2449 ], 2264 2450 "license": "BSD-3-Clause" 2265 2451 }, 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==", 2452 + "node_modules/fdir": { 2453 + "version": "6.5.0", 2270 2454 "dev": true, 2271 - "license": "ISC", 2272 - "dependencies": { 2273 - "reusify": "^1.0.4" 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 + } 2274 2466 } 2275 2467 }, 2276 2468 "node_modules/file-entry-cache": { 2277 2469 "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 2470 "dev": true, 2281 2471 "license": "MIT", 2282 2472 "dependencies": { ··· 2286 2476 "node": ">=16.0.0" 2287 2477 } 2288 2478 }, 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 2479 "node_modules/find-cache-dir": { 2303 2480 "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 2481 "dev": true, 2307 2482 "license": "MIT", 2308 2483 "dependencies": { ··· 2319 2494 }, 2320 2495 "node_modules/find-up": { 2321 2496 "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 2497 "dev": true, 2325 2498 "license": "MIT", 2326 2499 "dependencies": { ··· 2336 2509 }, 2337 2510 "node_modules/flat-cache": { 2338 2511 "version": "4.0.1", 2339 - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 2340 - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 2341 2512 "dev": true, 2342 2513 "license": "MIT", 2343 2514 "dependencies": { ··· 2350 2521 }, 2351 2522 "node_modules/flatted": { 2352 2523 "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 2524 "dev": true, 2356 2525 "license": "ISC" 2357 2526 }, 2358 2527 "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==", 2528 + "version": "10.1.0", 2362 2529 "dev": true, 2363 2530 "license": "MIT", 2364 2531 "dependencies": { ··· 2367 2534 "universalify": "^2.0.0" 2368 2535 }, 2369 2536 "engines": { 2370 - "node": ">=14.14" 2537 + "node": ">=12" 2371 2538 } 2372 2539 }, 2373 2540 "node_modules/fsevents": { ··· 2387 2554 }, 2388 2555 "node_modules/function-bind": { 2389 2556 "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 2557 "dev": true, 2393 2558 "license": "MIT", 2394 2559 "funding": { ··· 2397 2562 }, 2398 2563 "node_modules/gensync": { 2399 2564 "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 2565 "dev": true, 2403 2566 "license": "MIT", 2404 2567 "engines": { ··· 2407 2570 }, 2408 2571 "node_modules/glob-parent": { 2409 2572 "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 2573 "dev": true, 2413 2574 "license": "ISC", 2414 2575 "dependencies": { ··· 2419 2580 } 2420 2581 }, 2421 2582 "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==", 2583 + "version": "16.5.0", 2425 2584 "dev": true, 2426 2585 "license": "MIT", 2427 2586 "engines": { ··· 2433 2592 }, 2434 2593 "node_modules/graceful-fs": { 2435 2594 "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 2595 "dev": true, 2439 2596 "license": "ISC" 2440 2597 }, 2441 2598 "node_modules/graphemer": { 2442 2599 "version": "1.4.0", 2443 - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 2444 - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 2445 2600 "dev": true, 2446 2601 "license": "MIT" 2447 2602 }, 2448 2603 "node_modules/has-flag": { 2449 2604 "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 2605 "dev": true, 2453 2606 "license": "MIT", 2454 2607 "engines": { ··· 2457 2610 }, 2458 2611 "node_modules/hasown": { 2459 2612 "version": "2.0.2", 2460 - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 2461 - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 2462 2613 "dev": true, 2463 2614 "license": "MIT", 2464 2615 "dependencies": { ··· 2470 2621 }, 2471 2622 "node_modules/ignore": { 2472 2623 "version": "5.3.2", 2473 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 2474 - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 2475 2624 "dev": true, 2476 2625 "license": "MIT", 2477 2626 "engines": { ··· 2480 2629 }, 2481 2630 "node_modules/import-fresh": { 2482 2631 "version": "3.3.1", 2483 - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 2484 - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 2485 2632 "dev": true, 2486 2633 "license": "MIT", 2487 2634 "dependencies": { ··· 2497 2644 }, 2498 2645 "node_modules/import-lazy": { 2499 2646 "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 2647 "dev": true, 2503 2648 "license": "MIT", 2504 2649 "engines": { ··· 2507 2652 }, 2508 2653 "node_modules/imurmurhash": { 2509 2654 "version": "0.1.4", 2510 - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 2511 - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 2512 2655 "dev": true, 2513 2656 "license": "MIT", 2514 2657 "engines": { ··· 2517 2660 }, 2518 2661 "node_modules/is-core-module": { 2519 2662 "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 2663 "dev": true, 2523 2664 "license": "MIT", 2524 2665 "dependencies": { ··· 2533 2674 }, 2534 2675 "node_modules/is-extglob": { 2535 2676 "version": "2.1.1", 2536 - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 2537 - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 2538 2677 "dev": true, 2539 2678 "license": "MIT", 2540 2679 "engines": { ··· 2543 2682 }, 2544 2683 "node_modules/is-glob": { 2545 2684 "version": "4.0.3", 2546 - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 2547 - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 2548 2685 "dev": true, 2549 2686 "license": "MIT", 2550 2687 "dependencies": { ··· 2554 2691 "node": ">=0.10.0" 2555 2692 } 2556 2693 }, 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 2694 "node_modules/isexe": { 2568 2695 "version": "2.0.0", 2569 - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2570 - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2571 2696 "dev": true, 2572 2697 "license": "ISC" 2573 2698 }, 2574 2699 "node_modules/jju": { 2575 2700 "version": "1.4.0", 2576 - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", 2577 - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", 2578 2701 "dev": true, 2579 2702 "license": "MIT" 2580 2703 }, 2581 2704 "node_modules/js-tokens": { 2582 2705 "version": "4.0.0", 2583 - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2584 - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2585 2706 "dev": true, 2586 2707 "license": "MIT" 2587 2708 }, 2588 2709 "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==", 2710 + "version": "4.1.1", 2592 2711 "dev": true, 2593 2712 "license": "MIT", 2594 2713 "dependencies": { ··· 2598 2717 "js-yaml": "bin/js-yaml.js" 2599 2718 } 2600 2719 }, 2720 + "node_modules/js-yaml/node_modules/argparse": { 2721 + "version": "2.0.1", 2722 + "dev": true, 2723 + "license": "Python-2.0" 2724 + }, 2601 2725 "node_modules/jsesc": { 2602 2726 "version": "3.1.0", 2603 - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 2604 - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 2605 2727 "dev": true, 2606 2728 "license": "MIT", 2607 2729 "bin": { ··· 2613 2735 }, 2614 2736 "node_modules/json-buffer": { 2615 2737 "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 2738 "dev": true, 2619 2739 "license": "MIT" 2620 2740 }, 2621 2741 "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==", 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==", 2625 2745 "dev": true, 2626 2746 "license": "MIT" 2627 2747 }, 2628 2748 "node_modules/json-stable-stringify-without-jsonify": { 2629 2749 "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 2750 "dev": true, 2633 2751 "license": "MIT" 2634 2752 }, 2635 2753 "node_modules/json5": { 2636 2754 "version": "2.2.3", 2637 - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2638 - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2639 2755 "dev": true, 2640 2756 "license": "MIT", 2641 2757 "bin": { ··· 2647 2763 }, 2648 2764 "node_modules/jsonfile": { 2649 2765 "version": "6.2.0", 2650 - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", 2651 - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", 2652 2766 "dev": true, 2653 2767 "license": "MIT", 2654 2768 "dependencies": { ··· 2660 2774 }, 2661 2775 "node_modules/keyv": { 2662 2776 "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 2777 "dev": true, 2666 2778 "license": "MIT", 2667 2779 "dependencies": { ··· 2670 2782 }, 2671 2783 "node_modules/kolorist": { 2672 2784 "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 2785 "dev": true, 2676 2786 "license": "MIT" 2677 2787 }, 2678 2788 "node_modules/levn": { 2679 2789 "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 2790 "dev": true, 2683 2791 "license": "MIT", 2684 2792 "dependencies": { ··· 2691 2799 }, 2692 2800 "node_modules/lightningcss": { 2693 2801 "version": "1.30.2", 2694 - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", 2695 - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", 2696 2802 "dev": true, 2697 2803 "license": "MPL-2.0", 2698 2804 "dependencies": { ··· 2719 2825 "lightningcss-win32-x64-msvc": "1.30.2" 2720 2826 } 2721 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 + }, 2722 2849 "node_modules/lightningcss-darwin-arm64": { 2723 2850 "version": "1.30.2", 2724 2851 "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", ··· 2740 2867 "url": "https://opencollective.com/parcel" 2741 2868 } 2742 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 + }, 2743 3057 "node_modules/local-pkg": { 2744 3058 "version": "1.1.2", 2745 - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", 2746 - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", 2747 3059 "dev": true, 2748 3060 "license": "MIT", 2749 3061 "dependencies": { ··· 2760 3072 }, 2761 3073 "node_modules/locate-path": { 2762 3074 "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 3075 "dev": true, 2766 3076 "license": "MIT", 2767 3077 "dependencies": { ··· 2776 3086 }, 2777 3087 "node_modules/lodash": { 2778 3088 "version": "4.17.21", 2779 - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 2780 - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 2781 3089 "dev": true, 2782 3090 "license": "MIT" 2783 3091 }, 2784 3092 "node_modules/lodash.merge": { 2785 3093 "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 3094 "dev": true, 2789 3095 "license": "MIT" 2790 3096 }, 2791 3097 "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==", 3098 + "version": "6.0.0", 2795 3099 "dev": true, 2796 3100 "license": "ISC", 2797 3101 "dependencies": { 2798 - "yallist": "^3.0.2" 3102 + "yallist": "^4.0.0" 3103 + }, 3104 + "engines": { 3105 + "node": ">=10" 2799 3106 } 2800 3107 }, 2801 3108 "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==", 3109 + "version": "0.30.21", 2805 3110 "dev": true, 2806 3111 "license": "MIT", 2807 3112 "dependencies": { ··· 2810 3115 }, 2811 3116 "node_modules/make-dir": { 2812 3117 "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 3118 "dev": true, 2816 3119 "license": "MIT", 2817 3120 "dependencies": { ··· 2824 3127 "url": "https://github.com/sponsors/sindresorhus" 2825 3128 } 2826 3129 }, 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==", 3130 + "node_modules/make-dir/node_modules/semver": { 3131 + "version": "6.3.1", 2831 3132 "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" 3133 + "license": "ISC", 3134 + "bin": { 3135 + "semver": "bin/semver.js" 2849 3136 } 2850 3137 }, 2851 3138 "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==", 3139 + "version": "10.0.3", 2855 3140 "dev": true, 2856 3141 "license": "ISC", 2857 3142 "dependencies": { 2858 - "brace-expansion": "^1.1.7" 3143 + "@isaacs/brace-expansion": "^5.0.0" 2859 3144 }, 2860 3145 "engines": { 2861 - "node": "*" 3146 + "node": "20 || >=22" 3147 + }, 3148 + "funding": { 3149 + "url": "https://github.com/sponsors/isaacs" 2862 3150 } 2863 3151 }, 2864 3152 "node_modules/mlly": { 2865 3153 "version": "1.8.0", 2866 - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", 2867 - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", 2868 3154 "dev": true, 2869 3155 "license": "MIT", 2870 3156 "dependencies": { ··· 2874 3160 "ufo": "^1.6.1" 2875 3161 } 2876 3162 }, 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 3163 "node_modules/mlly/node_modules/pkg-types": { 2885 3164 "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 3165 "dev": true, 2889 3166 "license": "MIT", 2890 3167 "dependencies": { ··· 2892 3169 "mlly": "^1.7.4", 2893 3170 "pathe": "^2.0.1" 2894 3171 } 3172 + }, 3173 + "node_modules/mlly/node_modules/pkg-types/node_modules/confbox": { 3174 + "version": "0.1.8", 3175 + "dev": true, 3176 + "license": "MIT" 2895 3177 }, 2896 3178 "node_modules/ms": { 2897 3179 "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 3180 "dev": true, 2901 3181 "license": "MIT" 2902 3182 }, 2903 3183 "node_modules/nanoid": { 2904 3184 "version": "3.3.11", 2905 - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2906 - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2907 3185 "dev": true, 2908 3186 "funding": [ 2909 3187 { ··· 2921 3199 }, 2922 3200 "node_modules/natural-compare": { 2923 3201 "version": "1.4.0", 2924 - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 2925 - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 2926 3202 "dev": true, 2927 3203 "license": "MIT" 2928 3204 }, 2929 3205 "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==", 3206 + "version": "2.0.27", 2933 3207 "dev": true, 2934 3208 "license": "MIT" 2935 3209 }, 2936 3210 "node_modules/optionator": { 2937 3211 "version": "0.9.4", 2938 - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 2939 - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 2940 3212 "dev": true, 2941 3213 "license": "MIT", 2942 3214 "dependencies": { ··· 2953 3225 }, 2954 3226 "node_modules/p-limit": { 2955 3227 "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 3228 "dev": true, 2959 3229 "license": "MIT", 2960 3230 "dependencies": { ··· 2969 3239 }, 2970 3240 "node_modules/p-locate": { 2971 3241 "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 3242 "dev": true, 2975 3243 "license": "MIT", 2976 3244 "dependencies": { ··· 2985 3253 }, 2986 3254 "node_modules/p-try": { 2987 3255 "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 3256 "dev": true, 2991 3257 "license": "MIT", 2992 3258 "engines": { ··· 2995 3261 }, 2996 3262 "node_modules/parent-module": { 2997 3263 "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 3264 "dev": true, 3001 3265 "license": "MIT", 3002 3266 "dependencies": { ··· 3008 3272 }, 3009 3273 "node_modules/path-browserify": { 3010 3274 "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 3275 "dev": true, 3014 3276 "license": "MIT" 3015 3277 }, 3016 3278 "node_modules/path-exists": { 3017 3279 "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 3280 "dev": true, 3021 3281 "license": "MIT", 3022 3282 "engines": { ··· 3025 3285 }, 3026 3286 "node_modules/path-key": { 3027 3287 "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 3288 "dev": true, 3031 3289 "license": "MIT", 3032 3290 "engines": { ··· 3035 3293 }, 3036 3294 "node_modules/path-parse": { 3037 3295 "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 3296 "dev": true, 3041 3297 "license": "MIT" 3042 3298 }, 3043 3299 "node_modules/pathe": { 3044 3300 "version": "2.0.3", 3045 - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 3046 - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 3047 3301 "dev": true, 3048 3302 "license": "MIT" 3049 3303 }, 3050 3304 "node_modules/picocolors": { 3051 3305 "version": "1.1.1", 3052 - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 3053 - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 3054 3306 "dev": true, 3055 3307 "license": "ISC" 3056 3308 }, 3057 3309 "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==", 3310 + "version": "4.0.3", 3061 3311 "dev": true, 3062 3312 "license": "MIT", 3063 3313 "engines": { 3064 - "node": ">=8.6" 3314 + "node": ">=12" 3065 3315 }, 3066 3316 "funding": { 3067 3317 "url": "https://github.com/sponsors/jonschlinkert" ··· 3069 3319 }, 3070 3320 "node_modules/pkg-dir": { 3071 3321 "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 3322 "dev": true, 3075 3323 "license": "MIT", 3076 3324 "dependencies": { ··· 3082 3330 }, 3083 3331 "node_modules/pkg-dir/node_modules/find-up": { 3084 3332 "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 3333 "dev": true, 3088 3334 "license": "MIT", 3089 3335 "dependencies": { ··· 3094 3340 "node": ">=8" 3095 3341 } 3096 3342 }, 3097 - "node_modules/pkg-dir/node_modules/locate-path": { 3343 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path": { 3098 3344 "version": "5.0.0", 3099 - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 3100 - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 3101 3345 "dev": true, 3102 3346 "license": "MIT", 3103 3347 "dependencies": { ··· 3107 3351 "node": ">=8" 3108 3352 } 3109 3353 }, 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==", 3354 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { 3355 + "version": "4.1.0", 3114 3356 "dev": true, 3115 3357 "license": "MIT", 3116 3358 "dependencies": { 3117 - "p-try": "^2.0.0" 3359 + "p-limit": "^2.2.0" 3118 3360 }, 3119 3361 "engines": { 3120 - "node": ">=6" 3121 - }, 3122 - "funding": { 3123 - "url": "https://github.com/sponsors/sindresorhus" 3362 + "node": ">=8" 3124 3363 } 3125 3364 }, 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==", 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", 3130 3367 "dev": true, 3131 3368 "license": "MIT", 3132 3369 "dependencies": { 3133 - "p-limit": "^2.2.0" 3370 + "p-try": "^2.0.0" 3134 3371 }, 3135 3372 "engines": { 3136 - "node": ">=8" 3373 + "node": ">=6" 3374 + }, 3375 + "funding": { 3376 + "url": "https://github.com/sponsors/sindresorhus" 3137 3377 } 3138 3378 }, 3139 3379 "node_modules/pkg-types": { 3140 3380 "version": "2.3.0", 3141 - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", 3142 - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", 3143 3381 "dev": true, 3144 3382 "license": "MIT", 3145 3383 "dependencies": { ··· 3150 3388 }, 3151 3389 "node_modules/postcss": { 3152 3390 "version": "8.5.6", 3153 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 3154 - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 3155 3391 "dev": true, 3156 3392 "funding": [ 3157 3393 { ··· 3179 3415 }, 3180 3416 "node_modules/prelude-ls": { 3181 3417 "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 3418 "dev": true, 3185 3419 "license": "MIT", 3186 3420 "engines": { ··· 3189 3423 }, 3190 3424 "node_modules/punycode": { 3191 3425 "version": "2.3.1", 3192 - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 3193 - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 3194 3426 "dev": true, 3195 3427 "license": "MIT", 3196 3428 "engines": { ··· 3199 3431 }, 3200 3432 "node_modules/quansync": { 3201 3433 "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 3434 "dev": true, 3205 3435 "funding": [ 3206 3436 { ··· 3214 3444 ], 3215 3445 "license": "MIT" 3216 3446 }, 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 3447 "node_modules/react": { 3239 3448 "version": "19.2.0", 3240 - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", 3241 - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", 3242 3449 "dev": true, 3243 3450 "license": "MIT", 3451 + "peer": true, 3244 3452 "engines": { 3245 3453 "node": ">=0.10.0" 3246 3454 } 3247 3455 }, 3248 3456 "node_modules/react-dom": { 3249 3457 "version": "19.2.0", 3250 - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", 3251 - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", 3252 3458 "dev": true, 3253 3459 "license": "MIT", 3254 3460 "dependencies": { ··· 3259 3465 } 3260 3466 }, 3261 3467 "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==", 3468 + "version": "0.18.0", 3265 3469 "dev": true, 3266 3470 "license": "MIT", 3267 3471 "engines": { ··· 3270 3474 }, 3271 3475 "node_modules/require-from-string": { 3272 3476 "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 3477 "dev": true, 3276 3478 "license": "MIT", 3277 3479 "engines": { ··· 3279 3481 } 3280 3482 }, 3281 3483 "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==", 3484 + "version": "1.22.11", 3285 3485 "dev": true, 3286 3486 "license": "MIT", 3287 3487 "dependencies": { 3288 - "is-core-module": "^2.16.0", 3488 + "is-core-module": "^2.16.1", 3289 3489 "path-parse": "^1.0.7", 3290 3490 "supports-preserve-symlinks-flag": "^1.0.0" 3291 3491 }, ··· 3301 3501 }, 3302 3502 "node_modules/resolve-from": { 3303 3503 "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 3504 "dev": true, 3307 3505 "license": "MIT", 3308 3506 "engines": { 3309 3507 "node": ">=4" 3310 3508 } 3311 3509 }, 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 3510 "node_modules/rolldown": { 3324 3511 "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 3512 "dev": true, 3328 3513 "license": "MIT", 3514 + "peer": true, 3329 3515 "dependencies": { 3330 3516 "@oxc-project/types": "=0.93.0", 3331 3517 "@rolldown/pluginutils": "1.0.0-beta.41", ··· 3356 3542 }, 3357 3543 "node_modules/rolldown/node_modules/@rolldown/pluginutils": { 3358 3544 "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 3545 "dev": true, 3362 3546 "license": "MIT" 3363 3547 }, 3364 3548 "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==", 3549 + "version": "4.53.3", 3368 3550 "dev": true, 3369 3551 "license": "MIT", 3370 3552 "peer": true, ··· 3379 3561 "npm": ">=8.0.0" 3380 3562 }, 3381 3563 "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", 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", 3404 3586 "fsevents": "~2.3.2" 3405 3587 } 3406 3588 }, 3407 3589 "node_modules/rollup-plugin-typescript2": { 3408 3590 "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 3591 "dev": true, 3412 3592 "license": "MIT", 3413 3593 "dependencies": { ··· 3422 3602 "typescript": ">=2.4.0" 3423 3603 } 3424 3604 }, 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 3605 "node_modules/rollup-plugin-typescript2/node_modules/semver": { 3455 3606 "version": "7.7.3", 3456 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 3457 - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 3458 3607 "dev": true, 3459 3608 "license": "ISC", 3460 3609 "bin": { ··· 3464 3613 "node": ">=10" 3465 3614 } 3466 3615 }, 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 3616 "node_modules/scheduler": { 3492 3617 "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 3618 "dev": true, 3496 3619 "license": "MIT" 3497 3620 }, 3498 3621 "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==", 3622 + "version": "7.5.4", 3502 3623 "dev": true, 3503 3624 "license": "ISC", 3625 + "dependencies": { 3626 + "lru-cache": "^6.0.0" 3627 + }, 3504 3628 "bin": { 3505 3629 "semver": "bin/semver.js" 3630 + }, 3631 + "engines": { 3632 + "node": ">=10" 3506 3633 } 3507 3634 }, 3508 3635 "node_modules/shebang-command": { 3509 3636 "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 3637 "dev": true, 3513 3638 "license": "MIT", 3514 3639 "dependencies": { ··· 3520 3645 }, 3521 3646 "node_modules/shebang-regex": { 3522 3647 "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 3648 "dev": true, 3526 3649 "license": "MIT", 3527 3650 "engines": { ··· 3530 3653 }, 3531 3654 "node_modules/source-map": { 3532 3655 "version": "0.6.1", 3533 - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 3534 - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 3535 3656 "dev": true, 3536 3657 "license": "BSD-3-Clause", 3537 3658 "engines": { ··· 3540 3661 }, 3541 3662 "node_modules/source-map-js": { 3542 3663 "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 3664 "dev": true, 3546 3665 "license": "BSD-3-Clause", 3547 3666 "engines": { ··· 3550 3669 }, 3551 3670 "node_modules/source-map-support": { 3552 3671 "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 3672 "dev": true, 3556 3673 "license": "MIT", 3557 3674 "optional": true, 3558 - "peer": true, 3559 3675 "dependencies": { 3560 3676 "buffer-from": "^1.0.0", 3561 3677 "source-map": "^0.6.0" ··· 3563 3679 }, 3564 3680 "node_modules/sprintf-js": { 3565 3681 "version": "1.0.3", 3566 - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 3567 - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 3568 3682 "dev": true, 3569 3683 "license": "BSD-3-Clause" 3570 3684 }, 3571 3685 "node_modules/string-argv": { 3572 3686 "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 3687 "dev": true, 3576 3688 "license": "MIT", 3577 3689 "engines": { ··· 3580 3692 }, 3581 3693 "node_modules/strip-json-comments": { 3582 3694 "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 3695 "dev": true, 3586 3696 "license": "MIT", 3587 3697 "engines": { ··· 3592 3702 } 3593 3703 }, 3594 3704 "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==", 3705 + "version": "8.1.1", 3598 3706 "dev": true, 3599 3707 "license": "MIT", 3600 3708 "dependencies": { 3601 3709 "has-flag": "^4.0.0" 3602 3710 }, 3603 3711 "engines": { 3604 - "node": ">=8" 3712 + "node": ">=10" 3713 + }, 3714 + "funding": { 3715 + "url": "https://github.com/chalk/supports-color?sponsor=1" 3605 3716 } 3606 3717 }, 3607 3718 "node_modules/supports-preserve-symlinks-flag": { 3608 3719 "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 3720 "dev": true, 3612 3721 "license": "MIT", 3613 3722 "engines": { ··· 3617 3726 "url": "https://github.com/sponsors/ljharb" 3618 3727 } 3619 3728 }, 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 3729 "node_modules/tinyglobby": { 3642 3730 "version": "0.2.15", 3643 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 3644 - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 3645 3731 "dev": true, 3646 3732 "license": "MIT", 3647 3733 "dependencies": { ··· 3655 3741 "url": "https://github.com/sponsors/SuperchupuDev" 3656 3742 } 3657 3743 }, 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 3744 "node_modules/ts-api-utils": { 3703 3745 "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 3746 "dev": true, 3707 3747 "license": "MIT", 3708 3748 "engines": { ··· 3714 3754 }, 3715 3755 "node_modules/tslib": { 3716 3756 "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 3757 "dev": true, 3720 3758 "license": "0BSD" 3721 3759 }, 3722 3760 "node_modules/type-check": { 3723 3761 "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 3762 "dev": true, 3727 3763 "license": "MIT", 3728 3764 "dependencies": { ··· 3738 3774 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3739 3775 "dev": true, 3740 3776 "license": "Apache-2.0", 3777 + "peer": true, 3741 3778 "bin": { 3742 3779 "tsc": "bin/tsc", 3743 3780 "tsserver": "bin/tsserver" ··· 3747 3784 } 3748 3785 }, 3749 3786 "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==", 3787 + "version": "8.48.1", 3753 3788 "dev": true, 3754 3789 "license": "MIT", 3755 3790 "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" 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" 3760 3795 }, 3761 3796 "engines": { 3762 3797 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ··· 3772 3807 }, 3773 3808 "node_modules/ufo": { 3774 3809 "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 3810 "dev": true, 3778 3811 "license": "MIT" 3779 3812 }, 3780 3813 "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==", 3814 + "version": "7.16.0", 3784 3815 "dev": true, 3785 3816 "license": "MIT" 3786 3817 }, 3787 3818 "node_modules/universalify": { 3788 3819 "version": "2.0.1", 3789 - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 3790 - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 3791 3820 "dev": true, 3792 3821 "license": "MIT", 3793 3822 "engines": { ··· 3795 3824 } 3796 3825 }, 3797 3826 "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==", 3827 + "version": "2.3.11", 3801 3828 "dev": true, 3802 3829 "license": "MIT", 3803 3830 "dependencies": { ··· 3812 3839 }, 3813 3840 "node_modules/unplugin-dts": { 3814 3841 "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 3842 "dev": true, 3818 3843 "license": "MIT", 3819 3844 "dependencies": { ··· 3864 3889 } 3865 3890 } 3866 3891 }, 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==", 3892 + "node_modules/unplugin-dts/node_modules/@rollup/pluginutils": { 3893 + "version": "5.3.0", 3871 3894 "dev": true, 3872 3895 "license": "MIT", 3896 + "dependencies": { 3897 + "@types/estree": "^1.0.0", 3898 + "estree-walker": "^2.0.2", 3899 + "picomatch": "^4.0.2" 3900 + }, 3873 3901 "engines": { 3874 - "node": ">=12" 3902 + "node": ">=14.0.0" 3903 + }, 3904 + "peerDependencies": { 3905 + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 3875 3906 }, 3876 - "funding": { 3877 - "url": "https://github.com/sponsors/jonschlinkert" 3907 + "peerDependenciesMeta": { 3908 + "rollup": { 3909 + "optional": true 3910 + } 3878 3911 } 3879 3912 }, 3880 3913 "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==", 3914 + "version": "1.1.4", 3884 3915 "dev": true, 3885 3916 "funding": [ 3886 3917 { ··· 3910 3941 }, 3911 3942 "node_modules/uri-js": { 3912 3943 "version": "4.4.1", 3913 - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 3914 - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 3915 3944 "dev": true, 3916 3945 "license": "BSD-2-Clause", 3917 3946 "dependencies": { ··· 3921 3950 "node_modules/vite": { 3922 3951 "name": "rolldown-vite", 3923 3952 "version": "7.1.14", 3924 - "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", 3925 - "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", 3926 3953 "dev": true, 3927 3954 "license": "MIT", 3955 + "peer": true, 3928 3956 "dependencies": { 3929 3957 "@oxc-project/runtime": "0.92.0", 3930 3958 "fdir": "^6.5.0", ··· 3995 4023 } 3996 4024 } 3997 4025 }, 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 4026 "node_modules/vscode-uri": { 4030 4027 "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 4028 "dev": true, 4034 4029 "license": "MIT" 4035 4030 }, 4036 4031 "node_modules/webpack-virtual-modules": { 4037 4032 "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 4033 "dev": true, 4041 4034 "license": "MIT" 4042 4035 }, 4043 4036 "node_modules/which": { 4044 4037 "version": "2.0.2", 4045 - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 4046 - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 4047 4038 "dev": true, 4048 4039 "license": "ISC", 4049 4040 "dependencies": { ··· 4058 4049 }, 4059 4050 "node_modules/word-wrap": { 4060 4051 "version": "1.2.5", 4061 - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 4062 - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 4063 4052 "dev": true, 4064 4053 "license": "MIT", 4065 4054 "engines": { ··· 4067 4056 } 4068 4057 }, 4069 4058 "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==", 4059 + "version": "4.0.0", 4073 4060 "dev": true, 4074 4061 "license": "ISC" 4075 4062 }, 4076 4063 "node_modules/yocto-queue": { 4077 4064 "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 4065 "dev": true, 4081 4066 "license": "MIT", 4082 4067 "engines": {
+2 -2
package.json
··· 1 1 { 2 2 "name": "atproto-ui", 3 - "version": "0.7.0", 3 + "version": "0.12.0", 4 4 "type": "module", 5 5 "description": "React components and hooks for rendering AT Protocol records.", 6 6 "main": "./lib-dist/index.js", ··· 43 43 "@atcute/bluesky": "^3.2.3", 44 44 "@atcute/client": "^4.0.3", 45 45 "@atcute/identity-resolver": "^1.1.3", 46 - "@atcute/tangled": "^1.0.6" 46 + "@atcute/tangled": "^1.0.10" 47 47 }, 48 48 "devDependencies": { 49 49 "@eslint/js": "^9.36.0",
+74 -1
src/App.tsx
··· 1 1 import React, { useState, useCallback, useRef } from "react"; 2 - import { AtProtoProvider } from "../lib"; 2 + import { AtProtoProvider, TangledRepo } from "../lib"; 3 3 import "../lib/styles.css"; 4 4 import "./App.css"; 5 5 ··· 12 12 } from "../lib/components/BlueskyPost"; 13 13 import { BlueskyPostList } from "../lib/components/BlueskyPostList"; 14 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"; 15 19 import { useDidResolution } from "../lib/hooks/useDidResolution"; 16 20 import { useLatestRecord } from "../lib/hooks/useLatestRecord"; 17 21 import type { FeedPostRecord } from "../lib/types/bluesky"; ··· 284 288 <h3 style={sectionHeaderStyle}>Recent Posts</h3> 285 289 <BlueskyPostList did={did} /> 286 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> 287 354 </div> 288 355 <div style={columnStackStyle}> 289 356 <section style={panelStyle}> ··· 381 448 did="nekomimi.pet" 382 449 rkey="3m45s553cys22" 383 450 showParent={false} 451 + /> 452 + </section> 453 + <section style={panelStyle}> 454 + <TangledRepo 455 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 456 + rkey="3m2sx5zpxzs22" 384 457 /> 385 458 </section> 386 459 <section style={panelStyle}>
+1 -1
tsconfig.lib.json
··· 19 19 "sourceMap": true, 20 20 "outDir": "./lib-dist", 21 21 "rootDir": "./lib", 22 - "types": ["@atcute/bluesky"] 22 + "types": ["@atcute/bluesky", "@atcute/tangled"] 23 23 }, 24 24 "include": ["lib/**/*.ts", "lib/**/*.tsx"] 25 25 }
+1 -2
vite.config.ts
··· 40 40 // Library build configuration 41 41 lib: { 42 42 entry: resolve(__dirname, 'lib/index.ts'), 43 + cssFileName: resolve(__dirname, 'lib/styles.css'), 43 44 name: 'atproto-ui', 44 45 formats: ['es'], 45 46 fileName: 'atproto-ui' ··· 71 72 } 72 73 } 73 74 }, 74 - sourcemap: false, 75 - minify: false 76 75 } 77 76 });