+22
.npmignore
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+1
-1
tsconfig.lib.json
+1
-2
vite.config.ts
+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
});