+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
+
```
+41
-3
README.md
+41
-3
README.md
···
1
# atproto-ui
2
3
-
A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically. [Live demo](https://atproto-ui.wisp.place).
4
5
## Screenshots
6
···
9
10
## Features
11
12
-
- **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledString`, `LeafletDocument`)
13
- **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching)
14
- **Customizable theming** - Override CSS variables to match your app's design
15
- **Composable hooks** - Build custom renderers with protocol primitives
16
- Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients
···
111
<LeafletDocument did={did} rkey={rkey} record={documentRecord} />
112
```
113
114
## API Reference
115
116
### Components
···
181
182
## Demo
183
184
-
Check out the [live demo](https://atproto-ui.netlify.app/) to see all components in action.
185
186
### Running Locally
187
···
1
# atproto-ui
2
3
+
A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically as well as caching these so multiple components can render quickly. [Live demo](https://atproto-ui.wisp.place).
4
+
5
+
This project is mostly a wrapper on the extremely amazing work [Mary](https://mary.my.id/) has done with [atcute](https://tangled.org/@mary.my.id/atcute), please support it. I have to give thanks to [phil](https://bsky.app/profile/bad-example.com) for microcosm and slingshot. Incredible services being given for free that is responsible for why the components fetch data so quickly.
6
7
## Screenshots
8
···
11
12
## Features
13
14
+
- **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledRepo`, `LeafletDocument`)
15
- **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching)
16
+
- **Caching** - Blobs, DIDs, and records are cached so components which use the same ones can render even quicker
17
- **Customizable theming** - Override CSS variables to match your app's design
18
- **Composable hooks** - Build custom renderers with protocol primitives
19
- Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients
···
114
<LeafletDocument did={did} rkey={rkey} record={documentRecord} />
115
```
116
117
+
### Using atcute Directly
118
+
119
+
Use atcute directly to construct records and pass them to componentsโfully compatible!
120
+
121
+
```tsx
122
+
import { Client, simpleFetchHandler, ok } from '@atcute/client';
123
+
import type { AppBskyFeedPost } from '@atcute/bluesky';
124
+
import { BlueskyPost } from 'atproto-ui';
125
+
126
+
// Create atcute client
127
+
const client = new Client({
128
+
handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' })
129
+
});
130
+
131
+
// Fetch a record
132
+
const data = await ok(
133
+
client.get('com.atproto.repo.getRecord', {
134
+
params: {
135
+
repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq',
136
+
collection: 'app.bsky.feed.post',
137
+
rkey: '3m45rq4sjes2h'
138
+
}
139
+
})
140
+
);
141
+
142
+
const record = data.value as AppBskyFeedPost.Main;
143
+
144
+
// Pass atcute record directly to component!
145
+
<BlueskyPost
146
+
did="did:plc:ttdrpj45ibqunmfhdsb4zdwq"
147
+
rkey="3m45rq4sjes2h"
148
+
record={record}
149
+
/>
150
+
```
151
+
152
## API Reference
153
154
### Components
···
219
220
## Demo
221
222
+
Check out the [live demo](https://atproto-ui.wisp.place/) to see all components in action.
223
224
### Running Locally
225
+697
bun.lock
+697
bun.lock
···
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"configVersion": 1,
4
+
"workspaces": {
5
+
"": {
6
+
"name": "atproto-ui",
7
+
"dependencies": {
8
+
"@atcute/atproto": "^3.1.7",
9
+
"@atcute/bluesky": "^3.2.3",
10
+
"@atcute/client": "^4.0.3",
11
+
"@atcute/identity-resolver": "^1.1.3",
12
+
"@atcute/tangled": "^1.0.10",
13
+
},
14
+
"devDependencies": {
15
+
"@eslint/js": "^9.36.0",
16
+
"@microsoft/api-extractor": "^7.53.1",
17
+
"@types/node": "^24.6.0",
18
+
"@types/react": "^19.1.16",
19
+
"@types/react-dom": "^19.1.9",
20
+
"@vitejs/plugin-react": "^5.0.4",
21
+
"eslint": "^9.36.0",
22
+
"eslint-plugin-react-hooks": "^5.2.0",
23
+
"eslint-plugin-react-refresh": "^0.4.22",
24
+
"globals": "^16.4.0",
25
+
"react": "^19.1.1",
26
+
"react-dom": "^19.1.1",
27
+
"rollup-plugin-typescript2": "^0.36.0",
28
+
"typescript": "~5.9.3",
29
+
"typescript-eslint": "^8.45.0",
30
+
"unplugin-dts": "^1.0.0-beta.6",
31
+
"vite": "npm:rolldown-vite@7.1.14",
32
+
},
33
+
"peerDependencies": {
34
+
"react": "^18.2.0 || ^19.0.0",
35
+
"react-dom": "^18.2.0 || ^19.0.0",
36
+
},
37
+
"optionalPeers": [
38
+
"react-dom",
39
+
],
40
+
},
41
+
},
42
+
"packages": {
43
+
"@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="],
44
+
45
+
"@atcute/bluesky": ["@atcute/bluesky@3.2.11", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A=="],
46
+
47
+
"@atcute/client": ["@atcute/client@4.1.0", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ=="],
48
+
49
+
"@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="],
50
+
51
+
"@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.4", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@atcute/util-fetch": "^1.0.3", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA=="],
52
+
53
+
"@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="],
54
+
55
+
"@atcute/tangled": ["@atcute/tangled@1.0.12", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.3" } }, "sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA=="],
56
+
57
+
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.4", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg=="],
58
+
59
+
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
60
+
61
+
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
62
+
63
+
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
64
+
65
+
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
66
+
67
+
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
68
+
69
+
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
70
+
71
+
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
72
+
73
+
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
74
+
75
+
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
76
+
77
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
78
+
79
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
80
+
81
+
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
82
+
83
+
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
84
+
85
+
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
86
+
87
+
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
88
+
89
+
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
90
+
91
+
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
92
+
93
+
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
94
+
95
+
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
96
+
97
+
"@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="],
98
+
99
+
"@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
100
+
101
+
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
102
+
103
+
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
104
+
105
+
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
106
+
107
+
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
108
+
109
+
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
110
+
111
+
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
112
+
113
+
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
114
+
115
+
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
116
+
117
+
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
118
+
119
+
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
120
+
121
+
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
122
+
123
+
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
124
+
125
+
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
126
+
127
+
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
128
+
129
+
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
130
+
131
+
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
132
+
133
+
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
134
+
135
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
136
+
137
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
138
+
139
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
140
+
141
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
142
+
143
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
144
+
145
+
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.55.1", "", { "dependencies": { "@microsoft/api-extractor-model": "7.32.1", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0", "@rushstack/rig-package": "0.6.0", "@rushstack/terminal": "0.19.4", "@rushstack/ts-command-line": "5.1.4", "diff": "~8.0.2", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w=="],
146
+
147
+
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.32.1", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0" } }, "sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA=="],
148
+
149
+
"@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="],
150
+
151
+
"@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.0", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.12.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw=="],
152
+
153
+
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="],
154
+
155
+
"@oxc-project/runtime": ["@oxc-project/runtime@0.92.0", "", {}, "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw=="],
156
+
157
+
"@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="],
158
+
159
+
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="],
160
+
161
+
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="],
162
+
163
+
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ=="],
164
+
165
+
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.41", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g=="],
166
+
167
+
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm" }, "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA=="],
168
+
169
+
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg=="],
170
+
171
+
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg=="],
172
+
173
+
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g=="],
174
+
175
+
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q=="],
176
+
177
+
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.41", "", { "os": "none", "cpu": "arm64" }, "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg=="],
178
+
179
+
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.41", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ=="],
180
+
181
+
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "arm64" }, "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg=="],
182
+
183
+
"@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "ia32" }, "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw=="],
184
+
185
+
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "x64" }, "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg=="],
186
+
187
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="],
188
+
189
+
"@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="],
190
+
191
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="],
192
+
193
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="],
194
+
195
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="],
196
+
197
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="],
198
+
199
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="],
200
+
201
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="],
202
+
203
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="],
204
+
205
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="],
206
+
207
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="],
208
+
209
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="],
210
+
211
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="],
212
+
213
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="],
214
+
215
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="],
216
+
217
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="],
218
+
219
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="],
220
+
221
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="],
222
+
223
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="],
224
+
225
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="],
226
+
227
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="],
228
+
229
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="],
230
+
231
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="],
232
+
233
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="],
234
+
235
+
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.19.0", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A=="],
236
+
237
+
"@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.1.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA=="],
238
+
239
+
"@rushstack/rig-package": ["@rushstack/rig-package@0.6.0", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw=="],
240
+
241
+
"@rushstack/terminal": ["@rushstack/terminal@0.19.4", "", { "dependencies": { "@rushstack/node-core-library": "5.19.0", "@rushstack/problem-matcher": "0.1.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ=="],
242
+
243
+
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.1.4", "", { "dependencies": { "@rushstack/terminal": "0.19.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA=="],
244
+
245
+
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
246
+
247
+
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
248
+
249
+
"@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="],
250
+
251
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
252
+
253
+
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
254
+
255
+
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
256
+
257
+
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
258
+
259
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
260
+
261
+
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
262
+
263
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
264
+
265
+
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
266
+
267
+
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
268
+
269
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="],
270
+
271
+
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="],
272
+
273
+
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="],
274
+
275
+
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="],
276
+
277
+
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="],
278
+
279
+
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="],
280
+
281
+
"@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="],
282
+
283
+
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="],
284
+
285
+
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="],
286
+
287
+
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="],
288
+
289
+
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="],
290
+
291
+
"@volar/language-core": ["@volar/language-core@2.4.26", "", { "dependencies": { "@volar/source-map": "2.4.26" } }, "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A=="],
292
+
293
+
"@volar/source-map": ["@volar/source-map@2.4.26", "", {}, "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw=="],
294
+
295
+
"@volar/typescript": ["@volar/typescript@2.4.26", "", { "dependencies": { "@volar/language-core": "2.4.26", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA=="],
296
+
297
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
298
+
299
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
300
+
301
+
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
302
+
303
+
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
304
+
305
+
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
306
+
307
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
308
+
309
+
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
310
+
311
+
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
312
+
313
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
314
+
315
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="],
316
+
317
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
318
+
319
+
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
320
+
321
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
322
+
323
+
"caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="],
324
+
325
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
326
+
327
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
328
+
329
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
330
+
331
+
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
332
+
333
+
"compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="],
334
+
335
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
336
+
337
+
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
338
+
339
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
340
+
341
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
342
+
343
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
344
+
345
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
346
+
347
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
348
+
349
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
350
+
351
+
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
352
+
353
+
"electron-to-chromium": ["electron-to-chromium@1.5.263", "", {}, "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg=="],
354
+
355
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
356
+
357
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
358
+
359
+
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
360
+
361
+
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
362
+
363
+
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
364
+
365
+
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
366
+
367
+
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
368
+
369
+
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
370
+
371
+
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
372
+
373
+
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
374
+
375
+
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
376
+
377
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
378
+
379
+
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
380
+
381
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
382
+
383
+
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
384
+
385
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
386
+
387
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
388
+
389
+
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
390
+
391
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
392
+
393
+
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
394
+
395
+
"find-cache-dir": ["find-cache-dir@3.3.2", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" } }, "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig=="],
396
+
397
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
398
+
399
+
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
400
+
401
+
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
402
+
403
+
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
404
+
405
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
406
+
407
+
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
408
+
409
+
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
410
+
411
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
412
+
413
+
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
414
+
415
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
416
+
417
+
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
418
+
419
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
420
+
421
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
422
+
423
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
424
+
425
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
426
+
427
+
"import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="],
428
+
429
+
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
430
+
431
+
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
432
+
433
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
434
+
435
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
436
+
437
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
438
+
439
+
"jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="],
440
+
441
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
442
+
443
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
444
+
445
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
446
+
447
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
448
+
449
+
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
450
+
451
+
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
452
+
453
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
454
+
455
+
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
456
+
457
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
458
+
459
+
"kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
460
+
461
+
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
462
+
463
+
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
464
+
465
+
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
466
+
467
+
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
468
+
469
+
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
470
+
471
+
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
472
+
473
+
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
474
+
475
+
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
476
+
477
+
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
478
+
479
+
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
480
+
481
+
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
482
+
483
+
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
484
+
485
+
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
486
+
487
+
"local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="],
488
+
489
+
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
490
+
491
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
492
+
493
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
494
+
495
+
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
496
+
497
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
498
+
499
+
"make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
500
+
501
+
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
502
+
503
+
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
504
+
505
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
506
+
507
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
508
+
509
+
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
510
+
511
+
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
512
+
513
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
514
+
515
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
516
+
517
+
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
518
+
519
+
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
520
+
521
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
522
+
523
+
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
524
+
525
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
526
+
527
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
528
+
529
+
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
530
+
531
+
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
532
+
533
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
534
+
535
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
536
+
537
+
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
538
+
539
+
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
540
+
541
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
542
+
543
+
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
544
+
545
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
546
+
547
+
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
548
+
549
+
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
550
+
551
+
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
552
+
553
+
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
554
+
555
+
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
556
+
557
+
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
558
+
559
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
560
+
561
+
"rolldown": ["rolldown@1.0.0-beta.41", "", { "dependencies": { "@oxc-project/types": "=0.93.0", "@rolldown/pluginutils": "1.0.0-beta.41", "ansis": "=4.2.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-x64": "1.0.0-beta.41", "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg=="],
562
+
563
+
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
564
+
565
+
"rollup-plugin-typescript2": ["rollup-plugin-typescript2@0.36.0", "", { "dependencies": { "@rollup/pluginutils": "^4.1.2", "find-cache-dir": "^3.3.2", "fs-extra": "^10.0.0", "semver": "^7.5.4", "tslib": "^2.6.2" }, "peerDependencies": { "rollup": ">=1.26.3", "typescript": ">=2.4.0" } }, "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw=="],
566
+
567
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
568
+
569
+
"semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="],
570
+
571
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
572
+
573
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
574
+
575
+
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
576
+
577
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
578
+
579
+
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
580
+
581
+
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
582
+
583
+
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
584
+
585
+
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
586
+
587
+
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
588
+
589
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
590
+
591
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
592
+
593
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
594
+
595
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
596
+
597
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
598
+
599
+
"typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="],
600
+
601
+
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
602
+
603
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
604
+
605
+
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
606
+
607
+
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
608
+
609
+
"unplugin-dts": ["unplugin-dts@1.0.0-beta.6", "", { "dependencies": { "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.17", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", "unplugin": "^2.3.2" }, "peerDependencies": { "@microsoft/api-extractor": ">=7", "@rspack/core": "^1", "@vue/language-core": "~3.0.1", "esbuild": "*", "rolldown": "*", "rollup": ">=3", "typescript": ">=4", "vite": ">=3", "webpack": "^4 || ^5" }, "optionalPeers": ["@microsoft/api-extractor", "@rspack/core", "@vue/language-core", "esbuild", "rolldown", "rollup", "vite", "webpack"] }, "sha512-+xbFv5aVFtLZFNBAKI4+kXmd2h+T42/AaP8Bsp0YP/je/uOTN94Ame2Xt3e9isZS+Z7/hrLCLbsVJh+saqFMfQ=="],
610
+
611
+
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
612
+
613
+
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
614
+
615
+
"vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="],
616
+
617
+
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
618
+
619
+
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
620
+
621
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
622
+
623
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
624
+
625
+
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
626
+
627
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
628
+
629
+
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
630
+
631
+
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
632
+
633
+
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
634
+
635
+
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
636
+
637
+
"@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
638
+
639
+
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
640
+
641
+
"@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
642
+
643
+
"@microsoft/api-extractor/typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
644
+
645
+
"@microsoft/tsdoc-config/ajv": ["ajv@8.12.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA=="],
646
+
647
+
"@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
648
+
649
+
"@rushstack/node-core-library/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="],
650
+
651
+
"@rushstack/node-core-library/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="],
652
+
653
+
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
654
+
655
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
656
+
657
+
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
658
+
659
+
"ajv-formats/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="],
660
+
661
+
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
662
+
663
+
"eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
664
+
665
+
"js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
666
+
667
+
"make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
668
+
669
+
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
670
+
671
+
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
672
+
673
+
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="],
674
+
675
+
"rollup-plugin-typescript2/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
676
+
677
+
"unplugin-dts/@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
678
+
679
+
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
680
+
681
+
"@microsoft/tsdoc-config/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
682
+
683
+
"@rushstack/node-core-library/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
684
+
685
+
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
686
+
687
+
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
688
+
689
+
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
690
+
691
+
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
692
+
693
+
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
694
+
695
+
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
696
+
}
697
+
}
+308
-111
lib/components/BlueskyPostList.tsx
+308
-111
lib/components/BlueskyPostList.tsx
···
4
type AuthorFeedReason,
5
type ReplyParentInfo,
6
} from "../hooks/usePaginatedRecords";
7
-
import type { FeedPostRecord } from "../types/bluesky";
8
import { useDidResolution } from "../hooks/useDidResolution";
9
import { BlueskyIcon } from "./BlueskyIcon";
10
import { parseAtUri } from "../utils/at-uri";
11
12
/**
13
* Options for rendering a paginated list of Bluesky posts.
···
72
73
if (error)
74
return (
75
-
<div style={{ padding: 8, color: "crimson" }}>
76
Failed to load posts.
77
</div>
78
);
···
116
record={record.value}
117
rkey={record.rkey}
118
did={actorPath}
119
reason={record.reason}
120
replyParent={record.replyParent}
121
hasDivider={idx < records.length - 1}
···
202
record: FeedPostRecord;
203
rkey: string;
204
did: string;
205
reason?: AuthorFeedReason;
206
replyParent?: ReplyParentInfo;
207
hasDivider: boolean;
···
211
record,
212
rkey,
213
did,
214
reason,
215
replyParent,
216
hasDivider,
217
}) => {
218
const text = record.text?.trim() ?? "";
219
const relative = record.createdAt
220
? formatRelativeTime(record.createdAt)
···
222
const absolute = record.createdAt
223
? new Date(record.createdAt).toLocaleString()
224
: undefined;
225
-
const href = `https://bsky.app/profile/${did}/post/${rkey}`;
226
-
const repostLabel =
227
-
reason?.$type === "app.bsky.feed.defs#reasonRepost"
228
-
? `${formatActor(reason.by) ?? "Someone"} reposted`
229
-
: undefined;
230
const parentUri = replyParent?.uri ?? record.reply?.parent?.uri;
231
-
const parentDid =
232
-
replyParent?.author?.did ??
233
-
(parentUri ? parseAtUri(parentUri)?.did : undefined);
234
-
const { handle: resolvedReplyHandle } = useDidResolution(
235
replyParent?.author?.handle ? undefined : parentDid,
236
);
237
-
const replyLabel = formatReplyTarget(
238
-
parentUri,
239
-
replyParent,
240
-
resolvedReplyHandle,
241
);
242
243
return (
244
-
<a
245
-
href={href}
246
-
target="_blank"
247
-
rel="noopener noreferrer"
248
style={{
249
-
...listStyles.row,
250
-
color: `var(--atproto-color-text)`,
251
-
borderBottom: hasDivider
252
-
? `1px solid var(--atproto-color-border)`
253
-
: "none",
254
}}
255
>
256
-
{repostLabel && (
257
-
<span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}>
258
-
{repostLabel}
259
-
</span>
260
)}
261
-
{replyLabel && (
262
-
<span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}>
263
-
{replyLabel}
264
-
</span>
265
)}
266
-
{relative && (
267
-
<span
268
-
style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)` }}
269
-
title={absolute}
270
-
>
271
-
{relative}
272
-
</span>
273
-
)}
274
-
{text && (
275
-
<p style={{ ...listStyles.rowBody, color: `var(--atproto-color-text)` }}>
276
-
{text}
277
-
</p>
278
-
)}
279
-
{!text && (
280
-
<p
281
-
style={{
282
-
...listStyles.rowBody,
283
-
color: `var(--atproto-color-text)`,
284
-
fontStyle: "italic",
285
-
}}
286
-
>
287
-
No text content.
288
-
</p>
289
-
)}
290
-
</a>
291
);
292
};
293
···
352
display: "flex",
353
alignItems: "center",
354
justifyContent: "center",
355
-
//background: 'rgba(17, 133, 254, 0.14)',
356
borderRadius: "50%",
357
} satisfies React.CSSProperties,
358
headerText: {
···
380
fontSize: 13,
381
textAlign: "center",
382
} satisfies React.CSSProperties,
383
-
row: {
384
-
padding: "18px",
385
-
textDecoration: "none",
386
display: "flex",
387
flexDirection: "column",
388
-
gap: 6,
389
transition: "background-color 120ms ease",
390
} satisfies React.CSSProperties,
391
-
rowHeader: {
392
display: "flex",
393
-
gap: 6,
394
-
alignItems: "baseline",
395
fontSize: 13,
396
} satisfies React.CSSProperties,
397
-
rowTime: {
398
-
fontSize: 12,
399
fontWeight: 500,
400
} satisfies React.CSSProperties,
401
-
rowMeta: {
402
-
fontSize: 12,
403
fontWeight: 500,
404
-
letterSpacing: "0.6px",
405
} satisfies React.CSSProperties,
406
-
rowBody: {
407
margin: 0,
408
whiteSpace: "pre-wrap",
409
-
fontSize: 14,
410
-
lineHeight: 1.45,
411
} satisfies React.CSSProperties,
412
footer: {
413
display: "flex",
···
416
padding: "12px 18px",
417
borderTop: "1px solid transparent",
418
fontSize: 13,
419
-
} satisfies React.CSSProperties,
420
-
navButton: {
421
-
border: "none",
422
-
borderRadius: 999,
423
-
padding: "6px 12px",
424
-
fontSize: 13,
425
-
fontWeight: 500,
426
-
background: "transparent",
427
-
display: "flex",
428
-
alignItems: "center",
429
-
gap: 4,
430
-
transition: "background-color 120ms ease",
431
} satisfies React.CSSProperties,
432
pageChips: {
433
display: "flex",
···
472
};
473
474
export default BlueskyPostList;
475
-
476
-
function formatActor(actor?: { handle?: string; did?: string }) {
477
-
if (!actor) return undefined;
478
-
if (actor.handle) return `@${actor.handle}`;
479
-
if (actor.did) return `@${formatDid(actor.did)}`;
480
-
return undefined;
481
-
}
482
-
483
-
function formatReplyTarget(
484
-
parentUri?: string,
485
-
feedParent?: ReplyParentInfo,
486
-
resolvedHandle?: string,
487
-
) {
488
-
const directHandle = feedParent?.author?.handle;
489
-
const handle = directHandle ?? resolvedHandle;
490
-
if (handle) {
491
-
return `Replying to @${handle}`;
492
-
}
493
-
const parentDid = feedParent?.author?.did;
494
-
const targetUri = feedParent?.uri ?? parentUri;
495
-
if (!targetUri) return undefined;
496
-
const parsed = parseAtUri(targetUri);
497
-
const did = parentDid ?? parsed?.did;
498
-
if (!did) return undefined;
499
-
return `Replying to @${formatDid(did)}`;
500
-
}
···
4
type AuthorFeedReason,
5
type ReplyParentInfo,
6
} from "../hooks/usePaginatedRecords";
7
+
import type { FeedPostRecord, ProfileRecord } from "../types/bluesky";
8
import { useDidResolution } from "../hooks/useDidResolution";
9
import { BlueskyIcon } from "./BlueskyIcon";
10
import { parseAtUri } from "../utils/at-uri";
11
+
import { useAtProto } from "../providers/AtProtoProvider";
12
+
import { useAtProtoRecord } from "../hooks/useAtProtoRecord";
13
+
import { useBlob } from "../hooks/useBlob";
14
+
import { getAvatarCid } from "../utils/profile";
15
+
import { isBlobWithCdn } from "../utils/blob";
16
+
import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile";
17
+
import { RichText as BlueskyRichText } from "./RichText";
18
19
/**
20
* Options for rendering a paginated list of Bluesky posts.
···
79
80
if (error)
81
return (
82
+
<div role="alert" style={{ padding: 8, color: "crimson" }}>
83
Failed to load posts.
84
</div>
85
);
···
123
record={record.value}
124
rkey={record.rkey}
125
did={actorPath}
126
+
uri={record.uri}
127
reason={record.reason}
128
replyParent={record.replyParent}
129
hasDivider={idx < records.length - 1}
···
210
record: FeedPostRecord;
211
rkey: string;
212
did: string;
213
+
uri?: string;
214
reason?: AuthorFeedReason;
215
replyParent?: ReplyParentInfo;
216
hasDivider: boolean;
···
220
record,
221
rkey,
222
did,
223
+
uri,
224
reason,
225
replyParent,
226
hasDivider,
227
}) => {
228
+
const { blueskyAppBaseUrl } = useAtProto();
229
const text = record.text?.trim() ?? "";
230
const relative = record.createdAt
231
? formatRelativeTime(record.createdAt)
···
233
const absolute = record.createdAt
234
? new Date(record.createdAt).toLocaleString()
235
: undefined;
236
+
237
+
// Parse the URI to get the actual post's DID and rkey
238
+
const parsedUri = uri ? parseAtUri(uri) : undefined;
239
+
const postDid = parsedUri?.did ?? did;
240
+
const postRkey = parsedUri?.rkey ?? rkey;
241
+
const href = `${blueskyAppBaseUrl}/profile/${postDid}/post/${postRkey}`;
242
+
243
+
// Author profile and avatar
244
+
const { handle: authorHandle } = useDidResolution(postDid);
245
+
const { record: authorProfile } = useAtProtoRecord<ProfileRecord>({
246
+
did: postDid,
247
+
collection: BLUESKY_PROFILE_COLLECTION,
248
+
rkey: "self",
249
+
});
250
+
const authorDisplayName = authorProfile?.displayName;
251
+
const authorAvatar = authorProfile?.avatar;
252
+
const authorAvatarCdnUrl = isBlobWithCdn(authorAvatar) ? authorAvatar.cdnUrl : undefined;
253
+
const authorAvatarCid = authorAvatarCdnUrl ? undefined : getAvatarCid(authorProfile);
254
+
const { url: authorAvatarUrl } = useBlob(
255
+
postDid,
256
+
authorAvatarCid,
257
+
);
258
+
const finalAuthorAvatarUrl = authorAvatarCdnUrl ?? authorAvatarUrl;
259
+
260
+
// Repost metadata
261
+
const isRepost = reason?.$type === "app.bsky.feed.defs#reasonRepost";
262
+
const reposterDid = reason?.by?.did;
263
+
const { handle: reposterHandle } = useDidResolution(reposterDid);
264
+
const { record: reposterProfile } = useAtProtoRecord<ProfileRecord>({
265
+
did: reposterDid,
266
+
collection: BLUESKY_PROFILE_COLLECTION,
267
+
rkey: "self",
268
+
});
269
+
const reposterDisplayName = reposterProfile?.displayName;
270
+
const reposterAvatar = reposterProfile?.avatar;
271
+
const reposterAvatarCdnUrl = isBlobWithCdn(reposterAvatar) ? reposterAvatar.cdnUrl : undefined;
272
+
const reposterAvatarCid = reposterAvatarCdnUrl ? undefined : getAvatarCid(reposterProfile);
273
+
const { url: reposterAvatarUrl } = useBlob(
274
+
reposterDid,
275
+
reposterAvatarCid,
276
+
);
277
+
const finalReposterAvatarUrl = reposterAvatarCdnUrl ?? reposterAvatarUrl;
278
+
279
+
// Reply metadata
280
const parentUri = replyParent?.uri ?? record.reply?.parent?.uri;
281
+
const parentDid = replyParent?.author?.did ?? (parentUri ? parseAtUri(parentUri)?.did : undefined);
282
+
const { handle: parentHandle } = useDidResolution(
283
replyParent?.author?.handle ? undefined : parentDid,
284
);
285
+
const { record: parentProfile } = useAtProtoRecord<ProfileRecord>({
286
+
did: parentDid,
287
+
collection: BLUESKY_PROFILE_COLLECTION,
288
+
rkey: "self",
289
+
});
290
+
const parentAvatar = parentProfile?.avatar;
291
+
const parentAvatarCdnUrl = isBlobWithCdn(parentAvatar) ? parentAvatar.cdnUrl : undefined;
292
+
const parentAvatarCid = parentAvatarCdnUrl ? undefined : getAvatarCid(parentProfile);
293
+
const { url: parentAvatarUrl } = useBlob(
294
+
parentDid,
295
+
parentAvatarCid,
296
);
297
+
const finalParentAvatarUrl = parentAvatarCdnUrl ?? parentAvatarUrl;
298
+
299
+
const isReply = !!parentUri;
300
+
const replyTargetHandle = replyParent?.author?.handle ?? parentHandle;
301
+
302
+
const postPreview = text.slice(0, 100);
303
+
const ariaLabel = text
304
+
? `Post by ${authorDisplayName ?? authorHandle ?? did}: ${postPreview}${text.length > 100 ? "..." : ""}`
305
+
: `Post by ${authorDisplayName ?? authorHandle ?? did}`;
306
307
return (
308
+
<div
309
style={{
310
+
...listStyles.rowContainer,
311
+
borderBottom: hasDivider ? `1px solid var(--atproto-color-border)` : "none",
312
}}
313
>
314
+
{isRepost && (
315
+
<div style={listStyles.repostIndicator}>
316
+
{finalReposterAvatarUrl && (
317
+
<img
318
+
src={finalReposterAvatarUrl}
319
+
alt=""
320
+
style={listStyles.repostAvatar}
321
+
/>
322
+
)}
323
+
<svg
324
+
width="16"
325
+
height="16"
326
+
viewBox="0 0 16 16"
327
+
fill="none"
328
+
style={{ flexShrink: 0 }}
329
+
>
330
+
<path
331
+
d="M5.5 3.5L3 6L5.5 8.5M3 6H10C11.1046 6 12 6.89543 12 8V8.5M10.5 12.5L13 10L10.5 7.5M13 10H6C4.89543 10 4 9.10457 4 8V7.5"
332
+
stroke="var(--atproto-color-text-secondary)"
333
+
strokeWidth="1.5"
334
+
strokeLinecap="round"
335
+
strokeLinejoin="round"
336
+
/>
337
+
</svg>
338
+
<span style={{ ...listStyles.repostText, color: "var(--atproto-color-text-secondary)" }}>
339
+
{reposterDisplayName ?? reposterHandle ?? "Someone"} reposted
340
+
</span>
341
+
</div>
342
)}
343
+
344
+
{isReply && (
345
+
<div style={listStyles.replyIndicator}>
346
+
<svg
347
+
width="14"
348
+
height="14"
349
+
viewBox="0 0 14 14"
350
+
fill="none"
351
+
style={{ flexShrink: 0 }}
352
+
>
353
+
<path
354
+
d="M11 7H3M3 7L7 3M3 7L7 11"
355
+
stroke="#1185FE"
356
+
strokeWidth="1.5"
357
+
strokeLinecap="round"
358
+
strokeLinejoin="round"
359
+
/>
360
+
</svg>
361
+
<span style={{ ...listStyles.replyText, color: "var(--atproto-color-text-secondary)" }}>
362
+
Replying to
363
+
</span>
364
+
{finalParentAvatarUrl && (
365
+
<img
366
+
src={finalParentAvatarUrl}
367
+
alt=""
368
+
style={listStyles.replyAvatar}
369
+
/>
370
+
)}
371
+
<span style={{ color: "#1185FE", fontWeight: 600 }}>
372
+
@{replyTargetHandle ?? formatDid(parentDid ?? "")}
373
+
</span>
374
+
</div>
375
)}
376
+
377
+
<div style={listStyles.postContent}>
378
+
<div style={listStyles.avatarContainer}>
379
+
{finalAuthorAvatarUrl ? (
380
+
<img
381
+
src={finalAuthorAvatarUrl}
382
+
alt={authorDisplayName ?? authorHandle ?? "User avatar"}
383
+
style={listStyles.avatar}
384
+
/>
385
+
) : (
386
+
<div style={listStyles.avatarPlaceholder}>
387
+
{(authorDisplayName ?? authorHandle ?? "?")[0].toUpperCase()}
388
+
</div>
389
+
)}
390
+
</div>
391
+
392
+
<div style={listStyles.postMain}>
393
+
<div style={listStyles.postHeader}>
394
+
<a
395
+
href={`${blueskyAppBaseUrl}/profile/${postDid}`}
396
+
target="_blank"
397
+
rel="noopener noreferrer"
398
+
style={{ ...listStyles.authorName, color: "var(--atproto-color-text)" }}
399
+
onClick={(e) => e.stopPropagation()}
400
+
>
401
+
{authorDisplayName ?? authorHandle ?? formatDid(postDid)}
402
+
</a>
403
+
<span style={{ ...listStyles.authorHandle, color: "var(--atproto-color-text-secondary)" }}>
404
+
@{authorHandle ?? formatDid(postDid)}
405
+
</span>
406
+
<span style={{ ...listStyles.separator, color: "var(--atproto-color-text-secondary)" }}>ยท</span>
407
+
<span
408
+
style={{ ...listStyles.timestamp, color: "var(--atproto-color-text-secondary)" }}
409
+
title={absolute}
410
+
>
411
+
{relative}
412
+
</span>
413
+
</div>
414
+
415
+
<a
416
+
href={href}
417
+
target="_blank"
418
+
rel="noopener noreferrer"
419
+
aria-label={ariaLabel}
420
+
style={{ ...listStyles.postLink, color: "var(--atproto-color-text)" }}
421
+
>
422
+
{text && (
423
+
<p style={listStyles.postText}>
424
+
<BlueskyRichText text={text} facets={record.facets} />
425
+
</p>
426
+
)}
427
+
{!text && (
428
+
<p style={{ ...listStyles.postText, fontStyle: "italic", color: "var(--atproto-color-text-secondary)" }}>
429
+
No text content
430
+
</p>
431
+
)}
432
+
</a>
433
+
</div>
434
+
</div>
435
+
</div>
436
);
437
};
438
···
497
display: "flex",
498
alignItems: "center",
499
justifyContent: "center",
500
borderRadius: "50%",
501
} satisfies React.CSSProperties,
502
headerText: {
···
524
fontSize: 13,
525
textAlign: "center",
526
} satisfies React.CSSProperties,
527
+
rowContainer: {
528
+
padding: "16px",
529
display: "flex",
530
flexDirection: "column",
531
+
gap: 8,
532
transition: "background-color 120ms ease",
533
+
position: "relative",
534
} satisfies React.CSSProperties,
535
+
repostIndicator: {
536
display: "flex",
537
+
alignItems: "center",
538
+
gap: 8,
539
fontSize: 13,
540
+
fontWeight: 500,
541
+
paddingLeft: 8,
542
+
marginBottom: 4,
543
+
} satisfies React.CSSProperties,
544
+
repostAvatar: {
545
+
width: 16,
546
+
height: 16,
547
+
borderRadius: "50%",
548
+
objectFit: "cover",
549
} satisfies React.CSSProperties,
550
+
repostText: {
551
+
fontSize: 13,
552
fontWeight: 500,
553
} satisfies React.CSSProperties,
554
+
replyIndicator: {
555
+
display: "flex",
556
+
alignItems: "center",
557
+
gap: 8,
558
+
fontSize: 13,
559
fontWeight: 500,
560
+
paddingLeft: 8,
561
+
marginBottom: 4,
562
+
} satisfies React.CSSProperties,
563
+
replyAvatar: {
564
+
width: 16,
565
+
height: 16,
566
+
borderRadius: "50%",
567
+
objectFit: "cover",
568
+
} satisfies React.CSSProperties,
569
+
replyText: {
570
+
fontSize: 13,
571
+
fontWeight: 500,
572
+
} satisfies React.CSSProperties,
573
+
postContent: {
574
+
display: "flex",
575
+
gap: 12,
576
+
} satisfies React.CSSProperties,
577
+
avatarContainer: {
578
+
flexShrink: 0,
579
+
} satisfies React.CSSProperties,
580
+
avatar: {
581
+
width: 48,
582
+
height: 48,
583
+
borderRadius: "50%",
584
+
objectFit: "cover",
585
} satisfies React.CSSProperties,
586
+
avatarPlaceholder: {
587
+
width: 48,
588
+
height: 48,
589
+
borderRadius: "50%",
590
+
background: "var(--atproto-color-bg-elevated)",
591
+
color: "var(--atproto-color-text-secondary)",
592
+
display: "flex",
593
+
alignItems: "center",
594
+
justifyContent: "center",
595
+
fontSize: 18,
596
+
fontWeight: 600,
597
+
} satisfies React.CSSProperties,
598
+
postMain: {
599
+
flex: 1,
600
+
minWidth: 0,
601
+
display: "flex",
602
+
flexDirection: "column",
603
+
gap: 6,
604
+
} satisfies React.CSSProperties,
605
+
postHeader: {
606
+
display: "flex",
607
+
alignItems: "baseline",
608
+
gap: 6,
609
+
flexWrap: "wrap",
610
+
} satisfies React.CSSProperties,
611
+
authorName: {
612
+
fontWeight: 700,
613
+
fontSize: 15,
614
+
textDecoration: "none",
615
+
maxWidth: "200px",
616
+
overflow: "hidden",
617
+
textOverflow: "ellipsis",
618
+
whiteSpace: "nowrap",
619
+
} satisfies React.CSSProperties,
620
+
authorHandle: {
621
+
fontSize: 15,
622
+
fontWeight: 400,
623
+
maxWidth: "150px",
624
+
overflow: "hidden",
625
+
textOverflow: "ellipsis",
626
+
whiteSpace: "nowrap",
627
+
} satisfies React.CSSProperties,
628
+
separator: {
629
+
fontSize: 15,
630
+
fontWeight: 400,
631
+
} satisfies React.CSSProperties,
632
+
timestamp: {
633
+
fontSize: 15,
634
+
fontWeight: 400,
635
+
} satisfies React.CSSProperties,
636
+
postLink: {
637
+
textDecoration: "none",
638
+
display: "block",
639
+
} satisfies React.CSSProperties,
640
+
postText: {
641
margin: 0,
642
whiteSpace: "pre-wrap",
643
+
fontSize: 15,
644
+
lineHeight: 1.5,
645
+
wordBreak: "break-word",
646
} satisfies React.CSSProperties,
647
footer: {
648
display: "flex",
···
651
padding: "12px 18px",
652
borderTop: "1px solid transparent",
653
fontSize: 13,
654
} satisfies React.CSSProperties,
655
pageChips: {
656
display: "flex",
···
695
};
696
697
export default BlueskyPostList;
+143
lib/components/CurrentlyPlaying.tsx
+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;
+7
-4
lib/components/RichText.tsx
+7
-4
lib/components/RichText.tsx
···
1
import React from "react";
2
import type { AppBskyRichtextFacet } from "@atcute/bluesky";
3
import { createTextSegments, type TextSegment } from "../utils/richtext";
4
5
export interface RichTextProps {
6
text: string;
···
13
* Properly handles byte offsets and multi-byte characters.
14
*/
15
export const RichText: React.FC<RichTextProps> = ({ text, facets, style }) => {
16
const segments = createTextSegments(text, facets);
17
18
return (
19
<span style={style}>
20
{segments.map((segment, idx) => (
21
-
<RichTextSegment key={idx} segment={segment} />
22
))}
23
</span>
24
);
···
26
27
interface RichTextSegmentProps {
28
segment: TextSegment;
29
}
30
31
-
const RichTextSegment: React.FC<RichTextSegmentProps> = ({ segment }) => {
32
if (!segment.facet) {
33
return <>{segment.text}</>;
34
}
···
68
69
case "app.bsky.richtext.facet#mention": {
70
const mentionFeature = feature as AppBskyRichtextFacet.Mention;
71
-
const profileUrl = `https://bsky.app/profile/${mentionFeature.did}`;
72
return (
73
<a
74
href={profileUrl}
···
92
93
case "app.bsky.richtext.facet#tag": {
94
const tagFeature = feature as AppBskyRichtextFacet.Tag;
95
-
const tagUrl = `https://bsky.app/hashtag/${encodeURIComponent(tagFeature.tag)}`;
96
return (
97
<a
98
href={tagUrl}
···
1
import React from "react";
2
import type { AppBskyRichtextFacet } from "@atcute/bluesky";
3
import { createTextSegments, type TextSegment } from "../utils/richtext";
4
+
import { useAtProto } from "../providers/AtProtoProvider";
5
6
export interface RichTextProps {
7
text: string;
···
14
* Properly handles byte offsets and multi-byte characters.
15
*/
16
export const RichText: React.FC<RichTextProps> = ({ text, facets, style }) => {
17
+
const { blueskyAppBaseUrl } = useAtProto();
18
const segments = createTextSegments(text, facets);
19
20
return (
21
<span style={style}>
22
{segments.map((segment, idx) => (
23
+
<RichTextSegment key={idx} segment={segment} blueskyAppBaseUrl={blueskyAppBaseUrl} />
24
))}
25
</span>
26
);
···
28
29
interface RichTextSegmentProps {
30
segment: TextSegment;
31
+
blueskyAppBaseUrl: string;
32
}
33
34
+
const RichTextSegment: React.FC<RichTextSegmentProps> = ({ segment, blueskyAppBaseUrl }) => {
35
if (!segment.facet) {
36
return <>{segment.text}</>;
37
}
···
71
72
case "app.bsky.richtext.facet#mention": {
73
const mentionFeature = feature as AppBskyRichtextFacet.Mention;
74
+
const profileUrl = `${blueskyAppBaseUrl}/profile/${mentionFeature.did}`;
75
return (
76
<a
77
href={profileUrl}
···
95
96
case "app.bsky.richtext.facet#tag": {
97
const tagFeature = feature as AppBskyRichtextFacet.Tag;
98
+
const tagUrl = `${blueskyAppBaseUrl}/hashtag/${encodeURIComponent(tagFeature.tag)}`;
99
return (
100
<a
101
href={tagUrl}
+648
lib/components/SongHistoryList.tsx
+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;
+4
-2
lib/components/TangledString.tsx
+4
-2
lib/components/TangledString.tsx
···
1
import React from "react";
2
import { AtProtoRecord } from "../core/AtProtoRecord";
3
import { TangledStringRenderer } from "../renderers/TangledStringRenderer";
4
-
import type { TangledStringRecord } from "../renderers/TangledStringRenderer";
5
6
/**
7
* Props for rendering Tangled String records.
···
66
loadingIndicator,
67
colorScheme,
68
}) => {
69
const Comp: React.ComponentType<TangledStringRendererInjectedProps> =
70
renderer ?? ((props) => <TangledStringRenderer {...props} />);
71
const Wrapped: React.FC<{
···
78
colorScheme={colorScheme}
79
did={did}
80
rkey={rkey}
81
-
canonicalUrl={`https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`}
82
/>
83
);
84
···
1
import React from "react";
2
import { AtProtoRecord } from "../core/AtProtoRecord";
3
import { TangledStringRenderer } from "../renderers/TangledStringRenderer";
4
+
import type { TangledStringRecord } from "../types/tangled";
5
+
import { useAtProto } from "../providers/AtProtoProvider";
6
7
/**
8
* Props for rendering Tangled String records.
···
67
loadingIndicator,
68
colorScheme,
69
}) => {
70
+
const { tangledBaseUrl } = useAtProto();
71
const Comp: React.ComponentType<TangledStringRendererInjectedProps> =
72
renderer ?? ((props) => <TangledStringRenderer {...props} />);
73
const Wrapped: React.FC<{
···
80
colorScheme={colorScheme}
81
did={did}
82
rkey={rkey}
83
+
canonicalUrl={`${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`}
84
/>
85
);
86
+68
-6
lib/core/AtProtoRecord.tsx
+68
-6
lib/core/AtProtoRecord.tsx
···
1
-
import React from "react";
2
import { useAtProtoRecord } from "../hooks/useAtProtoRecord";
3
4
/**
···
15
fallback?: React.ReactNode;
16
/** React node shown while the record is being fetched. */
17
loadingIndicator?: React.ReactNode;
18
}
19
20
/**
···
61
*
62
* When no custom renderer is provided, displays the record as formatted JSON.
63
*
64
* @example
65
* ```tsx
66
* // Fetch mode - retrieves record from network
···
81
* />
82
* ```
83
*
84
* @param props - Either fetch props (did/collection/rkey) or prefetch props (record).
85
* @returns A rendered AT Protocol record with loading/error states handled.
86
*/
···
89
renderer: Renderer,
90
fallback = null,
91
loadingIndicator = "Loadingโฆ",
92
} = props;
93
const hasProvidedRecord = "record" in props;
94
const providedRecord = hasProvidedRecord ? props.record : undefined;
95
96
const {
97
record: fetchedRecord,
98
error,
99
loading,
100
} = useAtProtoRecord<T>({
101
-
did: hasProvidedRecord ? undefined : props.did,
102
-
collection: hasProvidedRecord ? undefined : props.collection,
103
-
rkey: hasProvidedRecord ? undefined : props.rkey,
104
});
105
106
-
const record = providedRecord ?? fetchedRecord;
107
-
const isLoading = loading && !providedRecord;
108
109
if (error && !record) return <>{fallback}</>;
110
if (!record) return <>{isLoading ? loadingIndicator : fallback}</>;
···
1
+
import React, { useState, useEffect, useRef } from "react";
2
import { useAtProtoRecord } from "../hooks/useAtProtoRecord";
3
4
/**
···
15
fallback?: React.ReactNode;
16
/** React node shown while the record is being fetched. */
17
loadingIndicator?: React.ReactNode;
18
+
/** Auto-refresh interval in milliseconds. When set, the record will be refetched at this interval. */
19
+
refreshInterval?: number;
20
+
/** Comparison function to determine if a record has changed. Used to prevent unnecessary re-renders during auto-refresh. */
21
+
compareRecords?: (prev: T | undefined, next: T | undefined) => boolean;
22
}
23
24
/**
···
65
*
66
* When no custom renderer is provided, displays the record as formatted JSON.
67
*
68
+
* **Auto-refresh**: Set `refreshInterval` to automatically refetch the record at the specified interval.
69
+
* The component intelligently avoids re-rendering if the record hasn't changed (using `compareRecords`).
70
+
*
71
* @example
72
* ```tsx
73
* // Fetch mode - retrieves record from network
···
88
* />
89
* ```
90
*
91
+
* @example
92
+
* ```tsx
93
+
* // Auto-refresh mode - refetches every 15 seconds
94
+
* <AtProtoRecord
95
+
* did="did:plc:example"
96
+
* collection="fm.teal.alpha.actor.status"
97
+
* rkey="self"
98
+
* refreshInterval={15000}
99
+
* compareRecords={(prev, next) => JSON.stringify(prev) === JSON.stringify(next)}
100
+
* renderer={MyCustomRenderer}
101
+
* />
102
+
* ```
103
+
*
104
* @param props - Either fetch props (did/collection/rkey) or prefetch props (record).
105
* @returns A rendered AT Protocol record with loading/error states handled.
106
*/
···
109
renderer: Renderer,
110
fallback = null,
111
loadingIndicator = "Loadingโฆ",
112
+
refreshInterval,
113
+
compareRecords,
114
} = props;
115
const hasProvidedRecord = "record" in props;
116
const providedRecord = hasProvidedRecord ? props.record : undefined;
117
118
+
// Extract fetch props for logging
119
+
const fetchDid = hasProvidedRecord ? undefined : (props as any).did;
120
+
const fetchCollection = hasProvidedRecord ? undefined : (props as any).collection;
121
+
const fetchRkey = hasProvidedRecord ? undefined : (props as any).rkey;
122
+
123
+
// State for managing auto-refresh
124
+
const [refreshKey, setRefreshKey] = useState(0);
125
+
const [stableRecord, setStableRecord] = useState<T | undefined>(providedRecord);
126
+
const previousRecordRef = useRef<T | undefined>(providedRecord);
127
+
128
+
// Auto-refresh interval
129
+
useEffect(() => {
130
+
if (!refreshInterval || hasProvidedRecord) return;
131
+
132
+
const interval = setInterval(() => {
133
+
setRefreshKey((prev) => prev + 1);
134
+
}, refreshInterval);
135
+
136
+
return () => clearInterval(interval);
137
+
}, [refreshInterval, hasProvidedRecord, fetchCollection, fetchDid]);
138
+
139
const {
140
record: fetchedRecord,
141
error,
142
loading,
143
} = useAtProtoRecord<T>({
144
+
did: fetchDid,
145
+
collection: fetchCollection,
146
+
rkey: fetchRkey,
147
+
bypassCache: !!refreshInterval && refreshKey > 0, // Bypass cache on auto-refresh (but not initial load)
148
+
_refreshKey: refreshKey, // Force hook to re-run
149
});
150
151
+
// Determine which record to use
152
+
const currentRecord = providedRecord ?? fetchedRecord;
153
+
154
+
// Handle record changes with optional comparison
155
+
useEffect(() => {
156
+
if (!currentRecord) return;
157
+
158
+
const hasChanged = compareRecords
159
+
? !compareRecords(previousRecordRef.current, currentRecord)
160
+
: previousRecordRef.current !== currentRecord;
161
+
162
+
if (hasChanged) {
163
+
setStableRecord(currentRecord);
164
+
previousRecordRef.current = currentRecord;
165
+
}
166
+
}, [currentRecord, compareRecords]);
167
+
168
+
const record = stableRecord;
169
+
const isLoading = loading && !providedRecord && !stableRecord;
170
171
if (error && !record) return <>{fallback}</>;
172
if (!record) return <>{isLoading ? loadingIndicator : fallback}</>;
+158
-29
lib/hooks/useAtProtoRecord.ts
+158
-29
lib/hooks/useAtProtoRecord.ts
···
1
-
import { useEffect, useState } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
import { createAtprotoClient } from "../utils/atproto-client";
5
import { useBlueskyAppview } from "./useBlueskyAppview";
6
7
/**
8
* Identifier trio required to address an AT Protocol record.
···
14
collection?: string;
15
/** Record key string uniquely identifying the record within the collection. */
16
rkey?: string;
17
}
18
19
/**
···
41
* @param did - DID (or handle before resolution) that owns the record.
42
* @param collection - NSID collection from which to fetch the record.
43
* @param rkey - Record key identifying the record within the collection.
44
* @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag.
45
*/
46
export function useAtProtoRecord<T = unknown>({
47
did: handleOrDid,
48
collection,
49
rkey,
50
}: AtProtoRecordKey): AtProtoRecordState<T> {
51
const isBlueskyCollection = collection?.startsWith("app.bsky.");
52
-
53
// Always call all hooks (React rules) - conditionally use results
54
const blueskyResult = useBlueskyAppview<T>({
55
did: isBlueskyCollection ? handleOrDid : undefined,
56
collection: isBlueskyCollection ? collection : undefined,
57
rkey: isBlueskyCollection ? rkey : undefined,
58
});
59
-
60
const {
61
did,
62
error: didError,
···
70
const [state, setState] = useState<AtProtoRecordState<T>>({
71
loading: !!(handleOrDid && collection && rkey),
72
});
73
74
useEffect(() => {
75
let cancelled = false;
···
87
});
88
return () => {
89
cancelled = true;
90
};
91
}
92
···
94
assignState({ loading: false, error: didError });
95
return () => {
96
cancelled = true;
97
};
98
}
99
···
101
assignState({ loading: false, error: endpointError });
102
return () => {
103
cancelled = true;
104
};
105
}
106
···
108
assignState({ loading: true, error: undefined });
109
return () => {
110
cancelled = true;
111
};
112
}
113
114
assignState({ loading: true, error: undefined, record: undefined });
115
116
-
(async () => {
117
-
try {
118
-
const { rpc } = await createAtprotoClient({
119
-
service: endpoint,
120
});
121
-
const res = await (
122
-
rpc as unknown as {
123
-
get: (
124
-
nsid: string,
125
-
opts: {
126
-
params: {
127
-
repo: string;
128
-
collection: string;
129
-
rkey: string;
130
-
};
131
-
},
132
-
) => Promise<{ ok: boolean; data: { value: T } }>;
133
}
134
-
).get("com.atproto.repo.getRecord", {
135
-
params: { repo: did, collection, rkey },
136
-
});
137
-
if (!res.ok) throw new Error("Failed to load record");
138
-
const record = (res.data as { value: T }).value;
139
-
assignState({ record, loading: false });
140
-
} catch (e) {
141
-
const err = e instanceof Error ? e : new Error(String(e));
142
-
assignState({ error: err, loading: false });
143
}
144
-
})();
145
146
return () => {
147
cancelled = true;
148
};
149
}, [
150
handleOrDid,
···
156
resolvingEndpoint,
157
didError,
158
endpointError,
159
]);
160
161
// Return Bluesky result for app.bsky.* collections
···
1
+
import { useEffect, useState, useRef } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
import { createAtprotoClient } from "../utils/atproto-client";
5
import { useBlueskyAppview } from "./useBlueskyAppview";
6
+
import { useAtProto } from "../providers/AtProtoProvider";
7
8
/**
9
* Identifier trio required to address an AT Protocol record.
···
15
collection?: string;
16
/** Record key string uniquely identifying the record within the collection. */
17
rkey?: string;
18
+
/** Force bypass cache and refetch from network. Useful for auto-refresh scenarios. */
19
+
bypassCache?: boolean;
20
+
/** Internal refresh trigger - changes to this value force a refetch. */
21
+
_refreshKey?: number;
22
}
23
24
/**
···
46
* @param did - DID (or handle before resolution) that owns the record.
47
* @param collection - NSID collection from which to fetch the record.
48
* @param rkey - Record key identifying the record within the collection.
49
+
* @param bypassCache - Force bypass cache and refetch from network. Useful for auto-refresh scenarios.
50
+
* @param _refreshKey - Internal parameter used to trigger refetches.
51
* @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag.
52
*/
53
export function useAtProtoRecord<T = unknown>({
54
did: handleOrDid,
55
collection,
56
rkey,
57
+
bypassCache = false,
58
+
_refreshKey = 0,
59
}: AtProtoRecordKey): AtProtoRecordState<T> {
60
+
const { recordCache } = useAtProto();
61
const isBlueskyCollection = collection?.startsWith("app.bsky.");
62
+
63
// Always call all hooks (React rules) - conditionally use results
64
const blueskyResult = useBlueskyAppview<T>({
65
did: isBlueskyCollection ? handleOrDid : undefined,
66
collection: isBlueskyCollection ? collection : undefined,
67
rkey: isBlueskyCollection ? rkey : undefined,
68
});
69
+
70
const {
71
did,
72
error: didError,
···
80
const [state, setState] = useState<AtProtoRecordState<T>>({
81
loading: !!(handleOrDid && collection && rkey),
82
});
83
+
84
+
const releaseRef = useRef<(() => void) | undefined>(undefined);
85
86
useEffect(() => {
87
let cancelled = false;
···
99
});
100
return () => {
101
cancelled = true;
102
+
if (releaseRef.current) {
103
+
releaseRef.current();
104
+
releaseRef.current = undefined;
105
+
}
106
};
107
}
108
···
110
assignState({ loading: false, error: didError });
111
return () => {
112
cancelled = true;
113
+
if (releaseRef.current) {
114
+
releaseRef.current();
115
+
releaseRef.current = undefined;
116
+
}
117
};
118
}
119
···
121
assignState({ loading: false, error: endpointError });
122
return () => {
123
cancelled = true;
124
+
if (releaseRef.current) {
125
+
releaseRef.current();
126
+
releaseRef.current = undefined;
127
+
}
128
};
129
}
130
···
132
assignState({ loading: true, error: undefined });
133
return () => {
134
cancelled = true;
135
+
if (releaseRef.current) {
136
+
releaseRef.current();
137
+
releaseRef.current = undefined;
138
+
}
139
};
140
}
141
142
assignState({ loading: true, error: undefined, record: undefined });
143
144
+
// Bypass cache if requested (for auto-refresh scenarios)
145
+
if (bypassCache) {
146
+
assignState({ loading: true, error: undefined });
147
+
148
+
// Skip cache and fetch directly
149
+
const controller = new AbortController();
150
+
151
+
const fetchPromise = (async () => {
152
+
try {
153
+
const { rpc } = await createAtprotoClient({
154
+
service: endpoint,
155
+
});
156
+
const res = await (
157
+
rpc as unknown as {
158
+
get: (
159
+
nsid: string,
160
+
opts: {
161
+
params: {
162
+
repo: string;
163
+
collection: string;
164
+
rkey: string;
165
+
};
166
+
},
167
+
) => Promise<{ ok: boolean; data: { value: T } }>;
168
+
}
169
+
).get("com.atproto.repo.getRecord", {
170
+
params: { repo: did, collection, rkey },
171
+
});
172
+
if (!res.ok) throw new Error("Failed to load record");
173
+
return (res.data as { value: T }).value;
174
+
} catch (err) {
175
+
// Provide helpful error for banned/unreachable Bluesky PDSes
176
+
if (endpoint.includes('.bsky.network')) {
177
+
throw new Error(
178
+
`Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.`
179
+
);
180
+
}
181
+
throw err;
182
+
}
183
+
})();
184
+
185
+
fetchPromise
186
+
.then((record) => {
187
+
if (!cancelled) {
188
+
assignState({ record, loading: false });
189
+
}
190
+
})
191
+
.catch((e) => {
192
+
if (!cancelled) {
193
+
const err = e instanceof Error ? e : new Error(String(e));
194
+
assignState({ error: err, loading: false });
195
+
}
196
});
197
+
198
+
return () => {
199
+
cancelled = true;
200
+
controller.abort();
201
+
};
202
+
}
203
+
204
+
// Use recordCache.ensure for deduplication and caching
205
+
const { promise, release } = recordCache.ensure<T>(
206
+
did,
207
+
collection,
208
+
rkey,
209
+
() => {
210
+
const controller = new AbortController();
211
+
212
+
const fetchPromise = (async () => {
213
+
try {
214
+
const { rpc } = await createAtprotoClient({
215
+
service: endpoint,
216
+
});
217
+
const res = await (
218
+
rpc as unknown as {
219
+
get: (
220
+
nsid: string,
221
+
opts: {
222
+
params: {
223
+
repo: string;
224
+
collection: string;
225
+
rkey: string;
226
+
};
227
+
},
228
+
) => Promise<{ ok: boolean; data: { value: T } }>;
229
+
}
230
+
).get("com.atproto.repo.getRecord", {
231
+
params: { repo: did, collection, rkey },
232
+
});
233
+
if (!res.ok) throw new Error("Failed to load record");
234
+
return (res.data as { value: T }).value;
235
+
} catch (err) {
236
+
// Provide helpful error for banned/unreachable Bluesky PDSes
237
+
if (endpoint.includes('.bsky.network')) {
238
+
throw new Error(
239
+
`Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.`
240
+
);
241
+
}
242
+
throw err;
243
}
244
+
})();
245
+
246
+
return {
247
+
promise: fetchPromise,
248
+
abort: () => controller.abort(),
249
+
};
250
}
251
+
);
252
+
253
+
releaseRef.current = release;
254
+
255
+
promise
256
+
.then((record) => {
257
+
if (!cancelled) {
258
+
assignState({ record, loading: false });
259
+
}
260
+
})
261
+
.catch((e) => {
262
+
if (!cancelled) {
263
+
const err = e instanceof Error ? e : new Error(String(e));
264
+
assignState({ error: err, loading: false });
265
+
}
266
+
});
267
268
return () => {
269
cancelled = true;
270
+
if (releaseRef.current) {
271
+
releaseRef.current();
272
+
releaseRef.current = undefined;
273
+
}
274
};
275
}, [
276
handleOrDid,
···
282
resolvingEndpoint,
283
didError,
284
endpointError,
285
+
recordCache,
286
+
bypassCache,
287
+
_refreshKey,
288
]);
289
290
// Return Bluesky result for app.bsky.* collections
+163
lib/hooks/useBacklinks.ts
+163
lib/hooks/useBacklinks.ts
···
···
1
+
import { useEffect, useState, useCallback, useRef } from "react";
2
+
3
+
/**
4
+
* Individual backlink record returned by Microcosm Constellation.
5
+
*/
6
+
export interface BacklinkRecord {
7
+
/** DID of the author who created the backlink. */
8
+
did: string;
9
+
/** Collection type of the backlink record (e.g., "sh.tangled.feed.star"). */
10
+
collection: string;
11
+
/** Record key of the backlink. */
12
+
rkey: string;
13
+
}
14
+
15
+
/**
16
+
* Response from Microcosm Constellation API.
17
+
*/
18
+
export interface BacklinksResponse {
19
+
/** Total count of backlinks. */
20
+
total: number;
21
+
/** Array of backlink records. */
22
+
records: BacklinkRecord[];
23
+
/** Cursor for pagination (optional). */
24
+
cursor?: string;
25
+
}
26
+
27
+
/**
28
+
* Parameters for fetching backlinks.
29
+
*/
30
+
export interface UseBacklinksParams {
31
+
/** The AT-URI subject to get backlinks for (e.g., "at://did:plc:xxx/sh.tangled.repo/yyy"). */
32
+
subject: string;
33
+
/** The source collection and path (e.g., "sh.tangled.feed.star:subject"). */
34
+
source: string;
35
+
/** Maximum number of results to fetch (default: 16, max: 100). */
36
+
limit?: number;
37
+
/** Base URL for the Microcosm Constellation API. */
38
+
constellationBaseUrl?: string;
39
+
/** Whether to automatically fetch backlinks on mount. */
40
+
enabled?: boolean;
41
+
}
42
+
43
+
const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue";
44
+
45
+
/**
46
+
* Hook to fetch backlinks from Microcosm Constellation API.
47
+
*
48
+
* Backlinks are records that reference another record. For example,
49
+
* `sh.tangled.feed.star` records are backlinks to `sh.tangled.repo` records,
50
+
* representing users who have starred a repository.
51
+
*
52
+
* @param params - Configuration for fetching backlinks
53
+
* @returns Object containing backlinks data, loading state, error, and refetch function
54
+
*
55
+
* @example
56
+
* ```tsx
57
+
* const { backlinks, loading, error, count } = useBacklinks({
58
+
* subject: "at://did:plc:example/sh.tangled.repo/3k2aexample",
59
+
* source: "sh.tangled.feed.star:subject",
60
+
* });
61
+
* ```
62
+
*/
63
+
export function useBacklinks({
64
+
subject,
65
+
source,
66
+
limit = 16,
67
+
constellationBaseUrl = DEFAULT_CONSTELLATION,
68
+
enabled = true,
69
+
}: UseBacklinksParams) {
70
+
const [backlinks, setBacklinks] = useState<BacklinkRecord[]>([]);
71
+
const [total, setTotal] = useState(0);
72
+
const [loading, setLoading] = useState(false);
73
+
const [error, setError] = useState<Error | undefined>(undefined);
74
+
const [cursor, setCursor] = useState<string | undefined>(undefined);
75
+
const abortControllerRef = useRef<AbortController | null>(null);
76
+
77
+
const fetchBacklinks = useCallback(
78
+
async (signal?: AbortSignal) => {
79
+
if (!subject || !source || !enabled) return;
80
+
81
+
try {
82
+
setLoading(true);
83
+
setError(undefined);
84
+
85
+
const baseUrl = constellationBaseUrl.endsWith("/")
86
+
? constellationBaseUrl.slice(0, -1)
87
+
: constellationBaseUrl;
88
+
89
+
const params = new URLSearchParams({
90
+
subject: subject,
91
+
source: source,
92
+
limit: limit.toString(),
93
+
});
94
+
95
+
const url = `${baseUrl}/xrpc/blue.microcosm.links.getBacklinks?${params}`;
96
+
97
+
const response = await fetch(url, { signal });
98
+
99
+
if (!response.ok) {
100
+
throw new Error(
101
+
`Failed to fetch backlinks: ${response.status} ${response.statusText}`,
102
+
);
103
+
}
104
+
105
+
const data: BacklinksResponse = await response.json();
106
+
setBacklinks(data.records || []);
107
+
setTotal(data.total || 0);
108
+
setCursor(data.cursor);
109
+
} catch (err) {
110
+
if (err instanceof Error && err.name === "AbortError") {
111
+
// Ignore abort errors
112
+
return;
113
+
}
114
+
setError(
115
+
err instanceof Error ? err : new Error("Unknown error fetching backlinks"),
116
+
);
117
+
} finally {
118
+
setLoading(false);
119
+
}
120
+
},
121
+
[subject, source, limit, constellationBaseUrl, enabled],
122
+
);
123
+
124
+
const refetch = useCallback(() => {
125
+
// Abort any in-flight request
126
+
if (abortControllerRef.current) {
127
+
abortControllerRef.current.abort();
128
+
}
129
+
130
+
const controller = new AbortController();
131
+
abortControllerRef.current = controller;
132
+
fetchBacklinks(controller.signal);
133
+
}, [fetchBacklinks]);
134
+
135
+
useEffect(() => {
136
+
if (!enabled) return;
137
+
138
+
const controller = new AbortController();
139
+
abortControllerRef.current = controller;
140
+
fetchBacklinks(controller.signal);
141
+
142
+
return () => {
143
+
controller.abort();
144
+
};
145
+
}, [fetchBacklinks, enabled]);
146
+
147
+
return {
148
+
/** Array of backlink records. */
149
+
backlinks,
150
+
/** Whether backlinks are currently being fetched. */
151
+
loading,
152
+
/** Error if fetch failed. */
153
+
error,
154
+
/** Pagination cursor (not yet implemented for pagination). */
155
+
cursor,
156
+
/** Total count of backlinks from the API. */
157
+
total,
158
+
/** Total count of backlinks (alias for total). */
159
+
count: total,
160
+
/** Function to manually refetch backlinks. */
161
+
refetch,
162
+
};
163
+
}
+140
-74
lib/hooks/useBlueskyAppview.ts
+140
-74
lib/hooks/useBlueskyAppview.ts
···
1
-
import { useEffect, useReducer } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
-
import { createAtprotoClient, SLINGSHOT_BASE_URL } from "../utils/atproto-client";
5
6
/**
7
* Extended blob reference that includes CDN URL from appview responses.
···
90
/** Source from which the record was successfully fetched. */
91
source?: "appview" | "slingshot" | "pds";
92
}
93
-
94
-
export const DEFAULT_APPVIEW_SERVICE = "https://public.api.bsky.app";
95
96
/**
97
* Maps Bluesky collection NSIDs to their corresponding appview API endpoints.
···
235
appviewService,
236
skipAppview = false,
237
}: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> {
238
const {
239
did,
240
error: didError,
···
253
source: undefined,
254
});
255
256
useEffect(() => {
257
let cancelled = false;
258
···
261
if (!cancelled) dispatch({ type: "RESET" });
262
return () => {
263
cancelled = true;
264
};
265
}
266
···
268
if (!cancelled) dispatch({ type: "SET_ERROR", error: didError });
269
return () => {
270
cancelled = true;
271
};
272
}
273
···
275
if (!cancelled) dispatch({ type: "SET_ERROR", error: endpointError });
276
return () => {
277
cancelled = true;
278
};
279
}
280
···
282
if (!cancelled) dispatch({ type: "SET_LOADING", loading: true });
283
return () => {
284
cancelled = true;
285
};
286
}
287
288
// Start fetching
289
dispatch({ type: "SET_LOADING", loading: true });
290
291
-
(async () => {
292
-
let lastError: Error | undefined;
293
294
-
// Tier 1: Try Bluesky appview API
295
-
if (!skipAppview && BLUESKY_COLLECTION_TO_ENDPOINT[collection]) {
296
-
try {
297
-
const result = await fetchFromAppview<T>(
298
-
did,
299
-
collection,
300
-
rkey,
301
-
appviewService ?? DEFAULT_APPVIEW_SERVICE,
302
-
);
303
-
if (!cancelled && result) {
304
-
dispatch({
305
-
type: "SET_SUCCESS",
306
-
record: result,
307
-
source: "appview",
308
-
});
309
-
return;
310
}
311
-
} catch (err) {
312
-
lastError = err as Error;
313
-
// Continue to next tier
314
-
}
315
}
316
317
-
// Tier 2: Try Slingshot getRecord
318
-
try {
319
-
const result = await fetchFromSlingshot<T>(did, collection, rkey);
320
-
if (!cancelled && result) {
321
dispatch({
322
type: "SET_SUCCESS",
323
-
record: result,
324
-
source: "slingshot",
325
});
326
-
return;
327
}
328
-
} catch (err) {
329
-
lastError = err as Error;
330
-
// Continue to next tier
331
-
}
332
-
333
-
// Tier 3: Try PDS directly
334
-
try {
335
-
const result = await fetchFromPds<T>(
336
-
did,
337
-
collection,
338
-
rkey,
339
-
pdsEndpoint,
340
-
);
341
-
if (!cancelled && result) {
342
dispatch({
343
-
type: "SET_SUCCESS",
344
-
record: result,
345
-
source: "pds",
346
});
347
-
return;
348
}
349
-
} catch (err) {
350
-
lastError = err as Error;
351
-
}
352
-
353
-
// All tiers failed
354
-
if (!cancelled) {
355
-
dispatch({
356
-
type: "SET_ERROR",
357
-
error:
358
-
lastError ??
359
-
new Error("Failed to fetch record from all sources"),
360
-
});
361
-
}
362
-
})();
363
364
return () => {
365
cancelled = true;
366
};
367
}, [
368
handleOrDid,
···
370
collection,
371
rkey,
372
pdsEndpoint,
373
-
appviewService,
374
skipAppview,
375
resolvingDid,
376
resolvingEndpoint,
377
didError,
378
endpointError,
379
]);
380
381
return state;
···
536
did: string,
537
collection: string,
538
rkey: string,
539
): Promise<T | undefined> {
540
-
const res = await callGetRecord<T>(SLINGSHOT_BASE_URL, did, collection, rkey);
541
if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`);
542
return res.data.value;
543
}
···
636
};
637
}> {
638
const { rpc } = await createAtprotoClient({ service });
639
return await (rpc as unknown as {
640
get: (
641
nsid: string,
···
648
};
649
}>;
650
}).get("com.atproto.repo.listRecords", {
651
-
params: {
652
-
repo: did,
653
-
collection,
654
-
limit,
655
-
cursor,
656
-
reverse: false,
657
-
},
658
});
659
}
660
···
1
+
import { useEffect, useReducer, useRef } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
+
import { createAtprotoClient } from "../utils/atproto-client";
5
+
import { useAtProto } from "../providers/AtProtoProvider";
6
7
/**
8
* Extended blob reference that includes CDN URL from appview responses.
···
91
/** Source from which the record was successfully fetched. */
92
source?: "appview" | "slingshot" | "pds";
93
}
94
95
/**
96
* Maps Bluesky collection NSIDs to their corresponding appview API endpoints.
···
234
appviewService,
235
skipAppview = false,
236
}: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> {
237
+
const { recordCache, blueskyAppviewService, resolver } = useAtProto();
238
+
const effectiveAppviewService = appviewService ?? blueskyAppviewService;
239
+
240
+
// Only use this hook for Bluesky collections (app.bsky.*)
241
+
const isBlueskyCollection = collection?.startsWith("app.bsky.");
242
+
243
const {
244
did,
245
error: didError,
···
258
source: undefined,
259
});
260
261
+
const releaseRef = useRef<(() => void) | undefined>(undefined);
262
+
263
useEffect(() => {
264
let cancelled = false;
265
···
268
if (!cancelled) dispatch({ type: "RESET" });
269
return () => {
270
cancelled = true;
271
+
if (releaseRef.current) {
272
+
releaseRef.current();
273
+
releaseRef.current = undefined;
274
+
}
275
+
};
276
+
}
277
+
278
+
// Return early if not a Bluesky collection - this hook should not be used for other lexicons
279
+
if (!isBlueskyCollection) {
280
+
if (!cancelled) dispatch({ type: "RESET" });
281
+
return () => {
282
+
cancelled = true;
283
+
if (releaseRef.current) {
284
+
releaseRef.current();
285
+
releaseRef.current = undefined;
286
+
}
287
};
288
}
289
···
291
if (!cancelled) dispatch({ type: "SET_ERROR", error: didError });
292
return () => {
293
cancelled = true;
294
+
if (releaseRef.current) {
295
+
releaseRef.current();
296
+
releaseRef.current = undefined;
297
+
}
298
};
299
}
300
···
302
if (!cancelled) dispatch({ type: "SET_ERROR", error: endpointError });
303
return () => {
304
cancelled = true;
305
+
if (releaseRef.current) {
306
+
releaseRef.current();
307
+
releaseRef.current = undefined;
308
+
}
309
};
310
}
311
···
313
if (!cancelled) dispatch({ type: "SET_LOADING", loading: true });
314
return () => {
315
cancelled = true;
316
+
if (releaseRef.current) {
317
+
releaseRef.current();
318
+
releaseRef.current = undefined;
319
+
}
320
};
321
}
322
323
// Start fetching
324
dispatch({ type: "SET_LOADING", loading: true });
325
326
+
// Use recordCache.ensure for deduplication and caching
327
+
const { promise, release } = recordCache.ensure<{ record: T; source: "appview" | "slingshot" | "pds" }>(
328
+
did,
329
+
collection,
330
+
rkey,
331
+
() => {
332
+
const controller = new AbortController();
333
+
334
+
const fetchPromise = (async (): Promise<{ record: T; source: "appview" | "slingshot" | "pds" }> => {
335
+
let lastError: Error | undefined;
336
+
337
+
// Tier 1: Try Bluesky appview API
338
+
if (!skipAppview && BLUESKY_COLLECTION_TO_ENDPOINT[collection]) {
339
+
try {
340
+
const result = await fetchFromAppview<T>(
341
+
did,
342
+
collection,
343
+
rkey,
344
+
effectiveAppviewService,
345
+
);
346
+
if (result) {
347
+
return { record: result, source: "appview" };
348
+
}
349
+
} catch (err) {
350
+
lastError = err as Error;
351
+
// Continue to next tier
352
+
}
353
+
}
354
355
+
// Tier 2: Try Slingshot getRecord
356
+
try {
357
+
const slingshotUrl = resolver.getSlingshotUrl();
358
+
const result = await fetchFromSlingshot<T>(did, collection, rkey, slingshotUrl);
359
+
if (result) {
360
+
return { record: result, source: "slingshot" };
361
+
}
362
+
} catch (err) {
363
+
lastError = err as Error;
364
+
// Continue to next tier
365
}
366
+
367
+
// Tier 3: Try PDS directly
368
+
try {
369
+
const result = await fetchFromPds<T>(
370
+
did,
371
+
collection,
372
+
rkey,
373
+
pdsEndpoint,
374
+
);
375
+
if (result) {
376
+
return { record: result, source: "pds" };
377
+
}
378
+
} catch (err) {
379
+
lastError = err as Error;
380
+
}
381
+
382
+
// All tiers failed - provide helpful error for banned/unreachable Bluesky PDSes
383
+
if (pdsEndpoint.includes('.bsky.network')) {
384
+
throw new Error(
385
+
`Record unavailable. The Bluesky PDS (${pdsEndpoint}) may be unreachable or the account may be banned.`
386
+
);
387
+
}
388
+
389
+
throw lastError ?? new Error("Failed to fetch record from all sources");
390
+
})();
391
+
392
+
return {
393
+
promise: fetchPromise,
394
+
abort: () => controller.abort(),
395
+
};
396
}
397
+
);
398
399
+
releaseRef.current = release;
400
+
401
+
promise
402
+
.then(({ record, source }) => {
403
+
if (!cancelled) {
404
dispatch({
405
type: "SET_SUCCESS",
406
+
record,
407
+
source,
408
});
409
}
410
+
})
411
+
.catch((err) => {
412
+
if (!cancelled) {
413
dispatch({
414
+
type: "SET_ERROR",
415
+
error: err instanceof Error ? err : new Error(String(err)),
416
});
417
}
418
+
});
419
420
return () => {
421
cancelled = true;
422
+
if (releaseRef.current) {
423
+
releaseRef.current();
424
+
releaseRef.current = undefined;
425
+
}
426
};
427
}, [
428
handleOrDid,
···
430
collection,
431
rkey,
432
pdsEndpoint,
433
+
effectiveAppviewService,
434
skipAppview,
435
resolvingDid,
436
resolvingEndpoint,
437
didError,
438
endpointError,
439
+
recordCache,
440
+
resolver,
441
]);
442
443
return state;
···
598
did: string,
599
collection: string,
600
rkey: string,
601
+
slingshotBaseUrl: string,
602
): Promise<T | undefined> {
603
+
const res = await callGetRecord<T>(slingshotBaseUrl, did, collection, rkey);
604
if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`);
605
return res.data.value;
606
}
···
699
};
700
}> {
701
const { rpc } = await createAtprotoClient({ service });
702
+
703
+
const params: Record<string, unknown> = {
704
+
repo: did,
705
+
collection,
706
+
limit,
707
+
cursor,
708
+
reverse: false,
709
+
};
710
+
711
return await (rpc as unknown as {
712
get: (
713
nsid: string,
···
720
};
721
}>;
722
}).get("com.atproto.repo.listRecords", {
723
+
params,
724
});
725
}
726
+5
-2
lib/hooks/useLatestRecord.ts
+5
-2
lib/hooks/useLatestRecord.ts
···
21
22
/**
23
* Fetches the most recent record from a collection using `listRecords(limit=3)`.
24
-
*
25
* Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly.
26
-
*
27
* Records with invalid timestamps (before 2023, when ATProto was created) are automatically
28
* skipped, and additional records are fetched to find a valid one.
29
*
30
* @param handleOrDid - Handle or DID that owns the collection.
31
* @param collection - NSID of the collection to query.
32
* @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error.
33
*/
34
export function useLatestRecord<T = unknown>(
35
handleOrDid: string | undefined,
36
collection: string,
37
): LatestRecordState<T> {
38
const {
39
did,
···
157
resolvingEndpoint,
158
didError,
159
endpointError,
160
]);
161
162
return state;
···
21
22
/**
23
* Fetches the most recent record from a collection using `listRecords(limit=3)`.
24
+
*
25
* Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly.
26
+
*
27
* Records with invalid timestamps (before 2023, when ATProto was created) are automatically
28
* skipped, and additional records are fetched to find a valid one.
29
*
30
* @param handleOrDid - Handle or DID that owns the collection.
31
* @param collection - NSID of the collection to query.
32
+
* @param refreshKey - Optional key that when changed, triggers a refetch. Use for auto-refresh scenarios.
33
* @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error.
34
*/
35
export function useLatestRecord<T = unknown>(
36
handleOrDid: string | undefined,
37
collection: string,
38
+
refreshKey?: number,
39
): LatestRecordState<T> {
40
const {
41
did,
···
159
resolvingEndpoint,
160
didError,
161
endpointError,
162
+
refreshKey,
163
]);
164
165
return state;
+4
-6
lib/hooks/usePaginatedRecords.ts
+4
-6
lib/hooks/usePaginatedRecords.ts
···
1
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
-
import {
5
-
DEFAULT_APPVIEW_SERVICE,
6
-
callAppviewRpc,
7
-
callListRecords
8
-
} from "./useBlueskyAppview";
9
10
/**
11
* Record envelope returned by paginated AT Protocol queries.
···
118
authorFeedService,
119
authorFeedActor,
120
}: UsePaginatedRecordsOptions): UsePaginatedRecordsResult<T> {
121
const {
122
did,
123
handle,
···
213
}
214
215
const res = await callAppviewRpc<AuthorFeedResponse>(
216
-
authorFeedService ?? DEFAULT_APPVIEW_SERVICE,
217
"app.bsky.feed.getAuthorFeed",
218
{
219
actor: actorIdentifier,
···
1
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
import { useDidResolution } from "./useDidResolution";
3
import { usePdsEndpoint } from "./usePdsEndpoint";
4
+
import { callAppviewRpc, callListRecords } from "./useBlueskyAppview";
5
+
import { useAtProto } from "../providers/AtProtoProvider";
6
7
/**
8
* Record envelope returned by paginated AT Protocol queries.
···
115
authorFeedService,
116
authorFeedActor,
117
}: UsePaginatedRecordsOptions): UsePaginatedRecordsResult<T> {
118
+
const { blueskyAppviewService } = useAtProto();
119
const {
120
did,
121
handle,
···
211
}
212
213
const res = await callAppviewRpc<AuthorFeedResponse>(
214
+
authorFeedService ?? blueskyAppviewService,
215
"app.bsky.feed.getAuthorFeed",
216
{
217
actor: actorIdentifier,
+104
lib/hooks/useRepoLanguages.ts
+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
export * from "./components/BlueskyPostList";
13
export * from "./components/BlueskyProfile";
14
export * from "./components/BlueskyQuotePost";
15
export * from "./components/LeafletDocument";
16
export * from "./components/TangledString";
17
18
// Hooks
19
export * from "./hooks/useAtProtoRecord";
20
export * from "./hooks/useBlob";
21
export * from "./hooks/useBlueskyAppview";
22
export * from "./hooks/useBlueskyProfile";
···
24
export * from "./hooks/useLatestRecord";
25
export * from "./hooks/usePaginatedRecords";
26
export * from "./hooks/usePdsEndpoint";
27
28
// Renderers
29
export * from "./renderers/BlueskyPostRenderer";
30
export * from "./renderers/BlueskyProfileRenderer";
31
export * from "./renderers/LeafletDocumentRenderer";
32
export * from "./renderers/TangledStringRenderer";
33
34
// Types
35
export * from "./types/bluesky";
36
export * from "./types/leaflet";
37
export * from "./types/theme";
38
39
// Utilities
···
12
export * from "./components/BlueskyPostList";
13
export * from "./components/BlueskyProfile";
14
export * from "./components/BlueskyQuotePost";
15
+
export * from "./components/GrainGallery";
16
export * from "./components/LeafletDocument";
17
+
export * from "./components/TangledRepo";
18
export * from "./components/TangledString";
19
+
export * from "./components/CurrentlyPlaying";
20
+
export * from "./components/LastPlayed";
21
+
export * from "./components/SongHistoryList";
22
23
// Hooks
24
export * from "./hooks/useAtProtoRecord";
25
+
export * from "./hooks/useBacklinks";
26
export * from "./hooks/useBlob";
27
export * from "./hooks/useBlueskyAppview";
28
export * from "./hooks/useBlueskyProfile";
···
30
export * from "./hooks/useLatestRecord";
31
export * from "./hooks/usePaginatedRecords";
32
export * from "./hooks/usePdsEndpoint";
33
+
export * from "./hooks/useRepoLanguages";
34
35
// Renderers
36
export * from "./renderers/BlueskyPostRenderer";
37
export * from "./renderers/BlueskyProfileRenderer";
38
+
export * from "./renderers/GrainGalleryRenderer";
39
export * from "./renderers/LeafletDocumentRenderer";
40
+
export * from "./renderers/TangledRepoRenderer";
41
export * from "./renderers/TangledStringRenderer";
42
+
export * from "./renderers/CurrentlyPlayingRenderer";
43
44
// Types
45
export * from "./types/bluesky";
46
+
export * from "./types/grain";
47
export * from "./types/leaflet";
48
+
export * from "./types/tangled";
49
+
export * from "./types/teal";
50
export * from "./types/theme";
51
52
// Utilities
+102
-9
lib/providers/AtProtoProvider.tsx
+102
-9
lib/providers/AtProtoProvider.tsx
···
5
useMemo,
6
useRef,
7
} from "react";
8
-
import { ServiceResolver, normalizeBaseUrl } from "../utils/atproto-client";
9
-
import { BlobCache, DidCache } from "../utils/cache";
10
11
/**
12
* Props for the AT Protocol context provider.
···
16
children: React.ReactNode;
17
/** Optional custom PLC directory URL. Defaults to https://plc.directory */
18
plcDirectory?: string;
19
}
20
21
/**
···
26
resolver: ServiceResolver;
27
/** Normalized PLC directory base URL. */
28
plcDirectory: string;
29
/** Cache for DID documents and handle mappings. */
30
didCache: DidCache;
31
/** Cache for fetched blob data. */
32
blobCache: BlobCache;
33
}
34
35
const AtProtoContext = createContext<AtProtoContextValue | undefined>(
···
75
export function AtProtoProvider({
76
children,
77
plcDirectory,
78
}: AtProtoProviderProps) {
79
const normalizedPlc = useMemo(
80
() =>
81
normalizeBaseUrl(
82
plcDirectory && plcDirectory.trim()
83
? plcDirectory
84
-
: "https://plc.directory",
85
),
86
[plcDirectory],
87
);
88
const resolver = useMemo(
89
-
() => new ServiceResolver({ plcDirectory: normalizedPlc }),
90
-
[normalizedPlc],
91
);
92
const cachesRef = useRef<{
93
didCache: DidCache;
94
blobCache: BlobCache;
95
} | null>(null);
96
if (!cachesRef.current) {
97
cachesRef.current = {
98
didCache: new DidCache(),
99
blobCache: new BlobCache(),
100
};
101
}
102
···
104
() => ({
105
resolver,
106
plcDirectory: normalizedPlc,
107
didCache: cachesRef.current!.didCache,
108
blobCache: cachesRef.current!.blobCache,
109
}),
110
-
[resolver, normalizedPlc],
111
);
112
113
return (
···
120
/**
121
* Hook that accesses the AT Protocol context provided by `AtProtoProvider`.
122
*
123
-
* This hook exposes the service resolver, DID cache, and blob cache for building
124
-
* custom AT Protocol functionality.
125
*
126
* @throws {Error} When called outside of an `AtProtoProvider`.
127
* @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL.
···
131
* import { useAtProto } from 'atproto-ui';
132
*
133
* function MyCustomComponent() {
134
-
* const { resolver, didCache, blobCache } = useAtProto();
135
* // Use the resolver and caches for custom AT Protocol operations
136
* }
137
* ```
···
5
useMemo,
6
useRef,
7
} from "react";
8
+
import { ServiceResolver, normalizeBaseUrl, DEFAULT_CONFIG } from "../utils/atproto-client";
9
+
import { BlobCache, DidCache, RecordCache } from "../utils/cache";
10
11
/**
12
* Props for the AT Protocol context provider.
···
16
children: React.ReactNode;
17
/** Optional custom PLC directory URL. Defaults to https://plc.directory */
18
plcDirectory?: string;
19
+
/** Optional custom identity service URL. Defaults to https://public.api.bsky.app */
20
+
identityService?: string;
21
+
/** Optional custom Slingshot service URL. Defaults to https://slingshot.microcosm.blue */
22
+
slingshotBaseUrl?: string;
23
+
/** Optional custom Bluesky appview service URL. Defaults to https://public.api.bsky.app */
24
+
blueskyAppviewService?: string;
25
+
/** Optional custom Bluesky app base URL for links. Defaults to https://bsky.app */
26
+
blueskyAppBaseUrl?: string;
27
+
/** Optional custom Tangled base URL for links. Defaults to https://tangled.org */
28
+
tangledBaseUrl?: string;
29
+
/** Optional custom Constellation API URL for backlinks. Defaults to https://constellation.microcosm.blue */
30
+
constellationBaseUrl?: string;
31
}
32
33
/**
···
38
resolver: ServiceResolver;
39
/** Normalized PLC directory base URL. */
40
plcDirectory: string;
41
+
/** Normalized Bluesky appview service URL. */
42
+
blueskyAppviewService: string;
43
+
/** Normalized Bluesky app base URL for links. */
44
+
blueskyAppBaseUrl: string;
45
+
/** Normalized Tangled base URL for links. */
46
+
tangledBaseUrl: string;
47
+
/** Normalized Constellation API base URL for backlinks. */
48
+
constellationBaseUrl: string;
49
/** Cache for DID documents and handle mappings. */
50
didCache: DidCache;
51
/** Cache for fetched blob data. */
52
blobCache: BlobCache;
53
+
/** Cache for fetched AT Protocol records. */
54
+
recordCache: RecordCache;
55
}
56
57
const AtProtoContext = createContext<AtProtoContextValue | undefined>(
···
97
export function AtProtoProvider({
98
children,
99
plcDirectory,
100
+
identityService,
101
+
slingshotBaseUrl,
102
+
blueskyAppviewService,
103
+
blueskyAppBaseUrl,
104
+
tangledBaseUrl,
105
+
constellationBaseUrl,
106
}: AtProtoProviderProps) {
107
const normalizedPlc = useMemo(
108
() =>
109
normalizeBaseUrl(
110
plcDirectory && plcDirectory.trim()
111
? plcDirectory
112
+
: DEFAULT_CONFIG.plcDirectory,
113
),
114
[plcDirectory],
115
);
116
+
const normalizedIdentity = useMemo(
117
+
() =>
118
+
normalizeBaseUrl(
119
+
identityService && identityService.trim()
120
+
? identityService
121
+
: DEFAULT_CONFIG.identityService,
122
+
),
123
+
[identityService],
124
+
);
125
+
const normalizedSlingshot = useMemo(
126
+
() =>
127
+
normalizeBaseUrl(
128
+
slingshotBaseUrl && slingshotBaseUrl.trim()
129
+
? slingshotBaseUrl
130
+
: DEFAULT_CONFIG.slingshotBaseUrl,
131
+
),
132
+
[slingshotBaseUrl],
133
+
);
134
+
const normalizedAppview = useMemo(
135
+
() =>
136
+
normalizeBaseUrl(
137
+
blueskyAppviewService && blueskyAppviewService.trim()
138
+
? blueskyAppviewService
139
+
: DEFAULT_CONFIG.blueskyAppviewService,
140
+
),
141
+
[blueskyAppviewService],
142
+
);
143
+
const normalizedBlueskyApp = useMemo(
144
+
() =>
145
+
normalizeBaseUrl(
146
+
blueskyAppBaseUrl && blueskyAppBaseUrl.trim()
147
+
? blueskyAppBaseUrl
148
+
: DEFAULT_CONFIG.blueskyAppBaseUrl,
149
+
),
150
+
[blueskyAppBaseUrl],
151
+
);
152
+
const normalizedTangled = useMemo(
153
+
() =>
154
+
normalizeBaseUrl(
155
+
tangledBaseUrl && tangledBaseUrl.trim()
156
+
? tangledBaseUrl
157
+
: DEFAULT_CONFIG.tangledBaseUrl,
158
+
),
159
+
[tangledBaseUrl],
160
+
);
161
+
const normalizedConstellation = useMemo(
162
+
() =>
163
+
normalizeBaseUrl(
164
+
constellationBaseUrl && constellationBaseUrl.trim()
165
+
? constellationBaseUrl
166
+
: DEFAULT_CONFIG.constellationBaseUrl,
167
+
),
168
+
[constellationBaseUrl],
169
+
);
170
const resolver = useMemo(
171
+
() => new ServiceResolver({
172
+
plcDirectory: normalizedPlc,
173
+
identityService: normalizedIdentity,
174
+
slingshotBaseUrl: normalizedSlingshot,
175
+
}),
176
+
[normalizedPlc, normalizedIdentity, normalizedSlingshot],
177
);
178
const cachesRef = useRef<{
179
didCache: DidCache;
180
blobCache: BlobCache;
181
+
recordCache: RecordCache;
182
} | null>(null);
183
if (!cachesRef.current) {
184
cachesRef.current = {
185
didCache: new DidCache(),
186
blobCache: new BlobCache(),
187
+
recordCache: new RecordCache(),
188
};
189
}
190
···
192
() => ({
193
resolver,
194
plcDirectory: normalizedPlc,
195
+
blueskyAppviewService: normalizedAppview,
196
+
blueskyAppBaseUrl: normalizedBlueskyApp,
197
+
tangledBaseUrl: normalizedTangled,
198
+
constellationBaseUrl: normalizedConstellation,
199
didCache: cachesRef.current!.didCache,
200
blobCache: cachesRef.current!.blobCache,
201
+
recordCache: cachesRef.current!.recordCache,
202
}),
203
+
[resolver, normalizedPlc, normalizedAppview, normalizedBlueskyApp, normalizedTangled, normalizedConstellation],
204
);
205
206
return (
···
213
/**
214
* Hook that accesses the AT Protocol context provided by `AtProtoProvider`.
215
*
216
+
* This hook exposes the service resolver, DID cache, blob cache, and record cache
217
+
* for building custom AT Protocol functionality.
218
*
219
* @throws {Error} When called outside of an `AtProtoProvider`.
220
* @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL.
···
224
* import { useAtProto } from 'atproto-ui';
225
*
226
* function MyCustomComponent() {
227
+
* const { resolver, didCache, blobCache, recordCache } = useAtProto();
228
* // Use the resolver and caches for custom AT Protocol operations
229
* }
230
* ```
+43
-8
lib/renderers/BlueskyPostRenderer.tsx
+43
-8
lib/renderers/BlueskyPostRenderer.tsx
···
56
57
if (error) {
58
return (
59
-
<div style={{ padding: 8, color: "crimson" }}>
60
Failed to load post.
61
</div>
62
);
63
}
64
-
if (loading && !record) return <div style={{ padding: 8 }}>Loadingโฆ</div>;
65
66
const text = record.text;
67
const createdDate = new Date(record.createdAt);
···
181
</div>
182
);
183
184
-
const Avatar: React.FC<{ avatarUrl?: string }> = ({ avatarUrl }) =>
185
avatarUrl ? (
186
-
<img src={avatarUrl} alt="avatar" style={baseStyles.avatarImg} />
187
) : (
188
-
<div style={baseStyles.avatarPlaceholder} aria-hidden />
189
);
190
191
const ReplyInfo: React.FC<{
···
278
279
const ThreadLayout: React.FC<LayoutProps> = (props) => (
280
<div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}>
281
-
<Avatar avatarUrl={props.avatarUrl} />
282
<div style={{ flex: 1, minWidth: 0 }}>
283
<div
284
style={{
···
326
const DefaultLayout: React.FC<LayoutProps> = (props) => (
327
<>
328
<header style={baseStyles.header}>
329
-
<Avatar avatarUrl={props.avatarUrl} />
330
<AuthorInfo
331
primaryName={props.primaryName}
332
authorDisplayName={props.authorDisplayName}
···
531
}
532
533
const PostImage: React.FC<PostImageProps> = ({ image, did }) => {
534
const imageBlob = image.image;
535
const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined;
536
const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob);
537
const { url: urlFromBlob, loading, error } = useBlob(did, cid);
538
const url = cdnUrl || urlFromBlob;
539
const alt = image.alt?.trim() || "Bluesky attachment";
540
541
const aspect =
542
image.aspectRatio && image.aspectRatio.height > 0
···
561
<img src={url} alt={alt} style={imagesBase.img} />
562
) : (
563
<div
564
style={{
565
...imagesBase.placeholder,
566
color: `var(--atproto-color-text-muted)`,
···
573
: "Image unavailable"}
574
</div>
575
)}
576
</div>
577
-
{image.alt && image.alt.trim().length > 0 && (
578
<figcaption
579
style={{
580
...imagesBase.caption,
···
621
caption: {
622
fontSize: 12,
623
lineHeight: 1.3,
624
} satisfies React.CSSProperties,
625
};
626
···
56
57
if (error) {
58
return (
59
+
<div role="alert" style={{ padding: 8, color: "crimson" }}>
60
Failed to load post.
61
</div>
62
);
63
}
64
+
if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโฆ</div>;
65
66
const text = record.text;
67
const createdDate = new Date(record.createdAt);
···
181
</div>
182
);
183
184
+
const Avatar: React.FC<{ avatarUrl?: string; name?: string }> = ({ avatarUrl, name }) =>
185
avatarUrl ? (
186
+
<img src={avatarUrl} alt={`${name || 'User'}'s profile picture`} style={baseStyles.avatarImg} />
187
) : (
188
+
<div style={baseStyles.avatarPlaceholder} aria-hidden="true" />
189
);
190
191
const ReplyInfo: React.FC<{
···
278
279
const ThreadLayout: React.FC<LayoutProps> = (props) => (
280
<div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}>
281
+
<Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} />
282
<div style={{ flex: 1, minWidth: 0 }}>
283
<div
284
style={{
···
326
const DefaultLayout: React.FC<LayoutProps> = (props) => (
327
<>
328
<header style={baseStyles.header}>
329
+
<Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} />
330
<AuthorInfo
331
primaryName={props.primaryName}
332
authorDisplayName={props.authorDisplayName}
···
531
}
532
533
const PostImage: React.FC<PostImageProps> = ({ image, did }) => {
534
+
const [showAltText, setShowAltText] = React.useState(false);
535
const imageBlob = image.image;
536
const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined;
537
const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob);
538
const { url: urlFromBlob, loading, error } = useBlob(did, cid);
539
const url = cdnUrl || urlFromBlob;
540
const alt = image.alt?.trim() || "Bluesky attachment";
541
+
const hasAlt = image.alt && image.alt.trim().length > 0;
542
543
const aspect =
544
image.aspectRatio && image.aspectRatio.height > 0
···
563
<img src={url} alt={alt} style={imagesBase.img} />
564
) : (
565
<div
566
+
role={error ? "alert" : "status"}
567
style={{
568
...imagesBase.placeholder,
569
color: `var(--atproto-color-text-muted)`,
···
576
: "Image unavailable"}
577
</div>
578
)}
579
+
{hasAlt && (
580
+
<button
581
+
onClick={() => setShowAltText(!showAltText)}
582
+
style={{
583
+
...imagesBase.altBadge,
584
+
background: showAltText
585
+
? `var(--atproto-color-text)`
586
+
: `var(--atproto-color-bg-secondary)`,
587
+
color: showAltText
588
+
? `var(--atproto-color-bg)`
589
+
: `var(--atproto-color-text)`,
590
+
}}
591
+
title="Toggle alt text"
592
+
aria-label="Toggle alt text"
593
+
>
594
+
ALT
595
+
</button>
596
+
)}
597
</div>
598
+
{hasAlt && showAltText && (
599
<figcaption
600
style={{
601
...imagesBase.caption,
···
642
caption: {
643
fontSize: 12,
644
lineHeight: 1.3,
645
+
} satisfies React.CSSProperties,
646
+
altBadge: {
647
+
position: "absolute",
648
+
bottom: 8,
649
+
right: 8,
650
+
padding: "4px 8px",
651
+
fontSize: 10,
652
+
fontWeight: 600,
653
+
letterSpacing: "0.5px",
654
+
border: "none",
655
+
borderRadius: 4,
656
+
cursor: "pointer",
657
+
transition: "background 150ms ease, color 150ms ease",
658
+
fontFamily: "system-ui, sans-serif",
659
} satisfies React.CSSProperties,
660
};
661
+47
-36
lib/renderers/BlueskyProfileRenderer.tsx
+47
-36
lib/renderers/BlueskyProfileRenderer.tsx
···
1
import React from "react";
2
import type { ProfileRecord } from "../types/bluesky";
3
import { BlueskyIcon } from "../components/BlueskyIcon";
4
5
export interface BlueskyProfileRendererProps {
6
record: ProfileRecord;
···
19
handle,
20
avatarUrl,
21
}) => {
22
23
if (error)
24
return (
25
-
<div style={{ padding: 8, color: "crimson" }}>
26
Failed to load profile.
27
</div>
28
);
29
-
if (loading && !record) return <div style={{ padding: 8 }}>Loadingโฆ</div>;
30
31
-
const profileUrl = `https://bsky.app/profile/${did}`;
32
const rawWebsite = record.website?.trim();
33
const websiteHref = rawWebsite
34
? rawWebsite.match(/^https?:\/\//i)
···
43
<div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}>
44
<div style={base.header}>
45
{avatarUrl ? (
46
-
<img src={avatarUrl} alt="avatar" style={base.avatarImg} />
47
) : (
48
<div
49
style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }}
50
-
aria-label="avatar"
51
/>
52
)}
53
<div style={{ flex: 1 }}>
···
69
{record.description}
70
</p>
71
)}
72
-
{record.createdAt && (
73
-
<div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}>
74
-
Joined {new Date(record.createdAt).toLocaleDateString()}
75
-
</div>
76
-
)}
77
-
<div style={base.links}>
78
-
{websiteHref && websiteLabel && (
79
<a
80
-
href={websiteHref}
81
target="_blank"
82
rel="noopener noreferrer"
83
style={{ ...base.link, color: `var(--atproto-color-link)` }}
84
>
85
-
{websiteLabel}
86
</a>
87
-
)}
88
-
<a
89
-
href={profileUrl}
90
-
target="_blank"
91
-
rel="noopener noreferrer"
92
-
style={{ ...base.link, color: `var(--atproto-color-link)` }}
93
-
>
94
-
View on Bluesky
95
-
</a>
96
-
</div>
97
-
<div style={base.iconCorner} aria-hidden>
98
-
<BlueskyIcon size={18} />
99
</div>
100
</div>
101
);
···
103
104
const base: Record<string, React.CSSProperties> = {
105
card: {
106
borderRadius: 12,
107
padding: 16,
108
fontFamily: "system-ui, sans-serif",
···
140
lineHeight: 1.4,
141
},
142
meta: {
143
-
marginTop: 12,
144
fontSize: 12,
145
},
146
pronouns: {
···
153
padding: "2px 8px",
154
marginTop: 6,
155
},
156
-
links: {
157
-
display: "flex",
158
-
flexDirection: "column",
159
-
gap: 8,
160
-
marginTop: 12,
161
-
},
162
link: {
163
display: "inline-flex",
164
alignItems: "center",
···
167
fontWeight: 600,
168
textDecoration: "none",
169
},
170
iconCorner: {
171
-
position: "absolute",
172
-
right: 12,
173
-
bottom: 12,
174
},
175
};
176
···
1
import React from "react";
2
import type { ProfileRecord } from "../types/bluesky";
3
import { BlueskyIcon } from "../components/BlueskyIcon";
4
+
import { useAtProto } from "../providers/AtProtoProvider";
5
6
export interface BlueskyProfileRendererProps {
7
record: ProfileRecord;
···
20
handle,
21
avatarUrl,
22
}) => {
23
+
const { blueskyAppBaseUrl } = useAtProto();
24
25
if (error)
26
return (
27
+
<div role="alert" style={{ padding: 8, color: "crimson" }}>
28
Failed to load profile.
29
</div>
30
);
31
+
if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโฆ</div>;
32
33
+
const profileUrl = `${blueskyAppBaseUrl}/profile/${did}`;
34
const rawWebsite = record.website?.trim();
35
const websiteHref = rawWebsite
36
? rawWebsite.match(/^https?:\/\//i)
···
45
<div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}>
46
<div style={base.header}>
47
{avatarUrl ? (
48
+
<img src={avatarUrl} alt={`${record.displayName || handle || did}'s profile picture`} style={base.avatarImg} />
49
) : (
50
<div
51
style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }}
52
+
aria-hidden="true"
53
/>
54
)}
55
<div style={{ flex: 1 }}>
···
71
{record.description}
72
</p>
73
)}
74
+
<div style={base.bottomRow}>
75
+
<div style={base.bottomLeft}>
76
+
{record.createdAt && (
77
+
<div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}>
78
+
Joined {new Date(record.createdAt).toLocaleDateString()}
79
+
</div>
80
+
)}
81
+
{websiteHref && websiteLabel && (
82
+
<a
83
+
href={websiteHref}
84
+
target="_blank"
85
+
rel="noopener noreferrer"
86
+
style={{ ...base.link, color: `var(--atproto-color-link)` }}
87
+
>
88
+
{websiteLabel}
89
+
</a>
90
+
)}
91
<a
92
+
href={profileUrl}
93
target="_blank"
94
rel="noopener noreferrer"
95
style={{ ...base.link, color: `var(--atproto-color-link)` }}
96
>
97
+
View on Bluesky
98
</a>
99
+
</div>
100
+
<div aria-hidden>
101
+
<BlueskyIcon size={18} />
102
+
</div>
103
</div>
104
</div>
105
);
···
107
108
const base: Record<string, React.CSSProperties> = {
109
card: {
110
+
display: "flex",
111
+
flexDirection: "column",
112
+
height: "100%",
113
borderRadius: 12,
114
padding: 16,
115
fontFamily: "system-ui, sans-serif",
···
147
lineHeight: 1.4,
148
},
149
meta: {
150
+
marginTop: 0,
151
fontSize: 12,
152
},
153
pronouns: {
···
160
padding: "2px 8px",
161
marginTop: 6,
162
},
163
link: {
164
display: "inline-flex",
165
alignItems: "center",
···
168
fontWeight: 600,
169
textDecoration: "none",
170
},
171
+
bottomRow: {
172
+
display: "flex",
173
+
alignItems: "flex-end",
174
+
justifyContent: "space-between",
175
+
marginTop: "auto",
176
+
paddingTop: 12,
177
+
},
178
+
bottomLeft: {
179
+
display: "flex",
180
+
flexDirection: "column",
181
+
gap: 8,
182
+
},
183
iconCorner: {
184
+
// Removed absolute positioning, now in flex layout
185
},
186
};
187
+749
lib/renderers/CurrentlyPlayingRenderer.tsx
+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;
+5
-3
lib/renderers/LeafletDocumentRenderer.tsx
+5
-3
lib/renderers/LeafletDocumentRenderer.tsx
···
1
import React, { useMemo, useRef } from "react";
2
import { useDidResolution } from "../hooks/useDidResolution";
3
import { useBlob } from "../hooks/useBlob";
4
import {
5
parseAtUri,
6
formatDidForLabel,
···
54
publicationBaseUrl,
55
publicationRecord,
56
}) => {
57
const authorDid = record.author?.startsWith("did:")
58
? record.author
59
: undefined;
···
78
: undefined);
79
const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel;
80
const authorHref = publicationUri
81
-
? `https://bsky.app/profile/${publicationUri.did}`
82
: undefined;
83
84
if (error)
···
105
timeStyle: "short",
106
})
107
: undefined;
108
-
const fallbackLeafletUrl = `https://bsky.app/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`;
109
const publicationRoot =
110
publicationBaseUrl ?? publicationRecord?.base_path ?? undefined;
111
const resolvedPublicationRoot = publicationRoot
···
117
publicationLeafletUrl ??
118
postUrl ??
119
(publicationUri
120
-
? `https://bsky.app/profile/${publicationUri.did}`
121
: undefined) ??
122
fallbackLeafletUrl;
123
···
1
import React, { useMemo, useRef } from "react";
2
import { useDidResolution } from "../hooks/useDidResolution";
3
import { useBlob } from "../hooks/useBlob";
4
+
import { useAtProto } from "../providers/AtProtoProvider";
5
import {
6
parseAtUri,
7
formatDidForLabel,
···
55
publicationBaseUrl,
56
publicationRecord,
57
}) => {
58
+
const { blueskyAppBaseUrl } = useAtProto();
59
const authorDid = record.author?.startsWith("did:")
60
? record.author
61
: undefined;
···
80
: undefined);
81
const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel;
82
const authorHref = publicationUri
83
+
? `${blueskyAppBaseUrl}/profile/${publicationUri.did}`
84
: undefined;
85
86
if (error)
···
107
timeStyle: "short",
108
})
109
: undefined;
110
+
const fallbackLeafletUrl = `${blueskyAppBaseUrl}/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`;
111
const publicationRoot =
112
publicationBaseUrl ?? publicationRecord?.base_path ?? undefined;
113
const resolvedPublicationRoot = publicationRoot
···
119
publicationLeafletUrl ??
120
postUrl ??
121
(publicationUri
122
+
? `${blueskyAppBaseUrl}/profile/${publicationUri.did}`
123
: undefined) ??
124
fallbackLeafletUrl;
125
+330
lib/renderers/TangledRepoRenderer.tsx
+330
lib/renderers/TangledRepoRenderer.tsx
···
···
1
+
import React from "react";
2
+
import type { TangledRepoRecord } from "../types/tangled";
3
+
import { useAtProto } from "../providers/AtProtoProvider";
4
+
import { useBacklinks } from "../hooks/useBacklinks";
5
+
import { useRepoLanguages } from "../hooks/useRepoLanguages";
6
+
7
+
export interface TangledRepoRendererProps {
8
+
record: TangledRepoRecord;
9
+
error?: Error;
10
+
loading: boolean;
11
+
did: string;
12
+
rkey: string;
13
+
canonicalUrl?: string;
14
+
showStarCount?: boolean;
15
+
branch?: string;
16
+
languages?: string[];
17
+
}
18
+
19
+
export const TangledRepoRenderer: React.FC<TangledRepoRendererProps> = ({
20
+
record,
21
+
error,
22
+
loading,
23
+
did,
24
+
rkey,
25
+
canonicalUrl,
26
+
showStarCount = true,
27
+
branch,
28
+
languages,
29
+
}) => {
30
+
const { tangledBaseUrl, constellationBaseUrl } = useAtProto();
31
+
32
+
// Construct the AT-URI for this repo record
33
+
const atUri = `at://${did}/sh.tangled.repo/${rkey}`;
34
+
35
+
// Fetch star backlinks
36
+
const {
37
+
count: starCount,
38
+
loading: starsLoading,
39
+
error: starsError,
40
+
} = useBacklinks({
41
+
subject: atUri,
42
+
source: "sh.tangled.feed.star:subject",
43
+
limit: 100,
44
+
constellationBaseUrl,
45
+
enabled: showStarCount,
46
+
});
47
+
48
+
// Extract knot server from record.knot (e.g., "knot.gaze.systems")
49
+
const knotUrl = record?.knot
50
+
? record.knot.startsWith("http://") || record.knot.startsWith("https://")
51
+
? new URL(record.knot).hostname
52
+
: record.knot
53
+
: undefined;
54
+
55
+
// Fetch language data from knot server only if languages not provided
56
+
const {
57
+
data: languagesData,
58
+
loading: _languagesLoading,
59
+
error: _languagesError,
60
+
} = useRepoLanguages({
61
+
knot: knotUrl,
62
+
did,
63
+
repoName: record?.name,
64
+
branch,
65
+
enabled: !languages && !!knotUrl && !!record?.name,
66
+
});
67
+
68
+
// Convert provided language names to the format expected by the renderer
69
+
const providedLanguagesData = languages
70
+
? {
71
+
languages: languages.map((name) => ({
72
+
name,
73
+
percentage: 0,
74
+
size: 0,
75
+
})),
76
+
ref: branch || "main",
77
+
totalFiles: 0,
78
+
totalSize: 0,
79
+
}
80
+
: undefined;
81
+
82
+
// Use provided languages or fetched languages
83
+
const finalLanguagesData = providedLanguagesData ?? languagesData;
84
+
85
+
if (error)
86
+
return (
87
+
<div role="alert" style={{ padding: 8, color: "crimson" }}>
88
+
Failed to load repository.
89
+
</div>
90
+
);
91
+
if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโฆ</div>;
92
+
93
+
// Construct the canonical URL: tangled.org/[did]/[repo-name]
94
+
const viewUrl =
95
+
canonicalUrl ??
96
+
`${tangledBaseUrl}/${did}/${encodeURIComponent(record.name)}`;
97
+
98
+
const tangledIcon = (
99
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 25 25" style={{ display: "block" }}>
100
+
<path fill="currentColor" d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"/>
101
+
</svg>
102
+
);
103
+
104
+
return (
105
+
<div
106
+
style={{
107
+
...base.container,
108
+
background: `var(--atproto-color-bg)`,
109
+
borderWidth: "1px",
110
+
borderStyle: "solid",
111
+
borderColor: `var(--atproto-color-border)`,
112
+
color: `var(--atproto-color-text)`,
113
+
}}
114
+
>
115
+
{/* Header with title and icons */}
116
+
<div
117
+
style={{
118
+
...base.header,
119
+
background: `var(--atproto-color-bg)`,
120
+
}}
121
+
>
122
+
<div style={base.headerTop}>
123
+
<strong
124
+
style={{
125
+
...base.repoName,
126
+
color: `var(--atproto-color-text)`,
127
+
}}
128
+
>
129
+
{record.name}
130
+
</strong>
131
+
<div style={base.headerRight}>
132
+
<a
133
+
href={viewUrl}
134
+
target="_blank"
135
+
rel="noopener noreferrer"
136
+
style={{
137
+
...base.iconLink,
138
+
color: `var(--atproto-color-text)`,
139
+
}}
140
+
title="View on Tangled"
141
+
>
142
+
{tangledIcon}
143
+
</a>
144
+
{record.source && (
145
+
<a
146
+
href={record.source}
147
+
target="_blank"
148
+
rel="noopener noreferrer"
149
+
style={{
150
+
...base.iconLink,
151
+
color: `var(--atproto-color-text)`,
152
+
}}
153
+
title="View source repository"
154
+
>
155
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}>
156
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
157
+
</svg>
158
+
</a>
159
+
)}
160
+
</div>
161
+
</div>
162
+
</div>
163
+
164
+
{/* Description */}
165
+
{record.description && (
166
+
<div
167
+
style={{
168
+
...base.description,
169
+
background: `var(--atproto-color-bg)`,
170
+
color: `var(--atproto-color-text-secondary)`,
171
+
}}
172
+
>
173
+
{record.description}
174
+
</div>
175
+
)}
176
+
177
+
{/* Languages and Stars */}
178
+
<div
179
+
style={{
180
+
...base.languageSection,
181
+
background: `var(--atproto-color-bg)`,
182
+
}}
183
+
>
184
+
{/* Languages */}
185
+
{finalLanguagesData && finalLanguagesData.languages.length > 0 && (() => {
186
+
const topLanguages = finalLanguagesData.languages
187
+
.filter((lang) => lang.name && (lang.percentage > 0 || finalLanguagesData.languages.every(l => l.percentage === 0)))
188
+
.sort((a, b) => b.percentage - a.percentage)
189
+
.slice(0, 2);
190
+
return topLanguages.length > 0 ? (
191
+
<div style={base.languageTags}>
192
+
{topLanguages.map((lang) => (
193
+
<span key={lang.name} style={base.languageTag}>
194
+
{lang.name}
195
+
</span>
196
+
))}
197
+
</div>
198
+
) : null;
199
+
})()}
200
+
201
+
{/* Right side: Stars and View on Tangled link */}
202
+
<div style={base.rightSection}>
203
+
{/* Stars */}
204
+
{showStarCount && (
205
+
<div
206
+
style={{
207
+
...base.starCountContainer,
208
+
color: `var(--atproto-color-text-secondary)`,
209
+
}}
210
+
>
211
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}>
212
+
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/>
213
+
</svg>
214
+
{starsLoading ? (
215
+
<span style={base.starCount}>...</span>
216
+
) : starsError ? (
217
+
<span style={base.starCount}>โ</span>
218
+
) : (
219
+
<span style={base.starCount}>{starCount}</span>
220
+
)}
221
+
</div>
222
+
)}
223
+
224
+
{/* View on Tangled link */}
225
+
<a
226
+
href={viewUrl}
227
+
target="_blank"
228
+
rel="noopener noreferrer"
229
+
style={{
230
+
...base.viewLink,
231
+
color: `var(--atproto-color-link)`,
232
+
}}
233
+
>
234
+
View on Tangled
235
+
</a>
236
+
</div>
237
+
</div>
238
+
</div>
239
+
);
240
+
};
241
+
242
+
const base: Record<string, React.CSSProperties> = {
243
+
container: {
244
+
fontFamily: "system-ui, sans-serif",
245
+
borderRadius: 6,
246
+
overflow: "hidden",
247
+
transition:
248
+
"background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease",
249
+
width: "100%",
250
+
},
251
+
header: {
252
+
padding: "16px",
253
+
display: "flex",
254
+
flexDirection: "column",
255
+
},
256
+
headerTop: {
257
+
display: "flex",
258
+
justifyContent: "space-between",
259
+
alignItems: "flex-start",
260
+
gap: 12,
261
+
},
262
+
headerRight: {
263
+
display: "flex",
264
+
alignItems: "center",
265
+
gap: 8,
266
+
},
267
+
repoName: {
268
+
fontFamily:
269
+
'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace',
270
+
fontSize: 18,
271
+
fontWeight: 600,
272
+
wordBreak: "break-word",
273
+
margin: 0,
274
+
},
275
+
iconLink: {
276
+
display: "flex",
277
+
alignItems: "center",
278
+
textDecoration: "none",
279
+
opacity: 0.7,
280
+
transition: "opacity 150ms ease",
281
+
},
282
+
description: {
283
+
padding: "0 16px 16px 16px",
284
+
fontSize: 14,
285
+
lineHeight: 1.5,
286
+
},
287
+
languageSection: {
288
+
padding: "0 16px 16px 16px",
289
+
display: "flex",
290
+
justifyContent: "space-between",
291
+
alignItems: "center",
292
+
gap: 12,
293
+
flexWrap: "wrap",
294
+
},
295
+
languageTags: {
296
+
display: "flex",
297
+
gap: 8,
298
+
flexWrap: "wrap",
299
+
},
300
+
languageTag: {
301
+
fontSize: 12,
302
+
fontWeight: 500,
303
+
padding: "4px 10px",
304
+
background: `var(--atproto-color-bg)`,
305
+
borderRadius: 12,
306
+
border: "1px solid var(--atproto-color-border)",
307
+
},
308
+
rightSection: {
309
+
display: "flex",
310
+
alignItems: "center",
311
+
gap: 12,
312
+
},
313
+
starCountContainer: {
314
+
display: "flex",
315
+
alignItems: "center",
316
+
gap: 4,
317
+
fontSize: 13,
318
+
},
319
+
starCount: {
320
+
fontSize: 13,
321
+
fontWeight: 500,
322
+
},
323
+
viewLink: {
324
+
fontSize: 13,
325
+
fontWeight: 500,
326
+
textDecoration: "none",
327
+
},
328
+
};
329
+
330
+
export default TangledRepoRenderer;
+4
-4
lib/renderers/TangledStringRenderer.tsx
+4
-4
lib/renderers/TangledStringRenderer.tsx
···
1
import React from "react";
2
-
import type { ShTangledString } from "@atcute/tangled";
3
-
4
-
export type TangledStringRecord = ShTangledString.Main;
5
6
export interface TangledStringRendererProps {
7
record: TangledStringRecord;
···
20
rkey,
21
canonicalUrl,
22
}) => {
23
24
if (error)
25
return (
···
31
32
const viewUrl =
33
canonicalUrl ??
34
-
`https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`;
35
const timestamp = new Date(record.createdAt).toLocaleString(undefined, {
36
dateStyle: "medium",
37
timeStyle: "short",
···
1
import React from "react";
2
+
import { useAtProto } from "../providers/AtProtoProvider";
3
+
import type { TangledStringRecord } from "../types/tangled";
4
5
export interface TangledStringRendererProps {
6
record: TangledStringRecord;
···
19
rkey,
20
canonicalUrl,
21
}) => {
22
+
const { tangledBaseUrl } = useAtProto();
23
24
if (error)
25
return (
···
31
32
const viewUrl =
33
canonicalUrl ??
34
+
`${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`;
35
const timestamp = new Date(record.createdAt).toLocaleString(undefined, {
36
dateStyle: "medium",
37
timeStyle: "short",
+59
-47
lib/styles.css
+59
-47
lib/styles.css
···
7
8
:root {
9
/* Light theme colors (default) */
10
-
--atproto-color-bg: #ffffff;
11
-
--atproto-color-bg-elevated: #f8fafc;
12
-
--atproto-color-bg-secondary: #f1f5f9;
13
--atproto-color-text: #0f172a;
14
--atproto-color-text-secondary: #475569;
15
--atproto-color-text-muted: #64748b;
16
-
--atproto-color-border: #e2e8f0;
17
-
--atproto-color-border-subtle: #cbd5e1;
18
--atproto-color-link: #2563eb;
19
--atproto-color-link-hover: #1d4ed8;
20
--atproto-color-error: #dc2626;
21
-
--atproto-color-button-bg: #f1f5f9;
22
-
--atproto-color-button-hover: #e2e8f0;
23
--atproto-color-button-text: #0f172a;
24
-
--atproto-color-code-bg: #f1f5f9;
25
-
--atproto-color-code-border: #e2e8f0;
26
-
--atproto-color-blockquote-border: #cbd5e1;
27
-
--atproto-color-blockquote-bg: #f8fafc;
28
-
--atproto-color-hr: #e2e8f0;
29
-
--atproto-color-image-bg: #f1f5f9;
30
--atproto-color-highlight: #fef08a;
31
}
32
33
/* Dark theme - can be applied via [data-theme="dark"] or .dark class */
34
[data-theme="dark"],
35
.dark {
36
-
--atproto-color-bg: #0f172a;
37
-
--atproto-color-bg-elevated: #1e293b;
38
-
--atproto-color-bg-secondary: #0b1120;
39
-
--atproto-color-text: #e2e8f0;
40
-
--atproto-color-text-secondary: #94a3b8;
41
-
--atproto-color-text-muted: #64748b;
42
-
--atproto-color-border: #1e293b;
43
-
--atproto-color-border-subtle: #334155;
44
--atproto-color-link: #60a5fa;
45
--atproto-color-link-hover: #93c5fd;
46
--atproto-color-error: #ef4444;
47
-
--atproto-color-button-bg: #1e293b;
48
-
--atproto-color-button-hover: #334155;
49
-
--atproto-color-button-text: #e2e8f0;
50
-
--atproto-color-code-bg: #0b1120;
51
-
--atproto-color-code-border: #1e293b;
52
-
--atproto-color-blockquote-border: #334155;
53
-
--atproto-color-blockquote-bg: #1e293b;
54
-
--atproto-color-hr: #334155;
55
-
--atproto-color-image-bg: #1e293b;
56
--atproto-color-highlight: #854d0e;
57
}
58
···
60
@media (prefers-color-scheme: dark) {
61
:root:not([data-theme]),
62
:root[data-theme="system"] {
63
-
--atproto-color-bg: #0f172a;
64
-
--atproto-color-bg-elevated: #1e293b;
65
-
--atproto-color-bg-secondary: #0b1120;
66
-
--atproto-color-text: #e2e8f0;
67
-
--atproto-color-text-secondary: #94a3b8;
68
-
--atproto-color-text-muted: #64748b;
69
-
--atproto-color-border: #1e293b;
70
-
--atproto-color-border-subtle: #334155;
71
--atproto-color-link: #60a5fa;
72
--atproto-color-link-hover: #93c5fd;
73
--atproto-color-error: #ef4444;
74
-
--atproto-color-button-bg: #1e293b;
75
-
--atproto-color-button-hover: #334155;
76
-
--atproto-color-button-text: #e2e8f0;
77
-
--atproto-color-code-bg: #0b1120;
78
-
--atproto-color-code-border: #1e293b;
79
-
--atproto-color-blockquote-border: #334155;
80
-
--atproto-color-blockquote-bg: #1e293b;
81
-
--atproto-color-hr: #334155;
82
-
--atproto-color-image-bg: #1e293b;
83
--atproto-color-highlight: #854d0e;
84
}
85
}
···
7
8
:root {
9
/* Light theme colors (default) */
10
+
--atproto-color-bg: #f5f7f9;
11
+
--atproto-color-bg-elevated: #f8f9fb;
12
+
--atproto-color-bg-secondary: #edf1f5;
13
--atproto-color-text: #0f172a;
14
--atproto-color-text-secondary: #475569;
15
--atproto-color-text-muted: #64748b;
16
+
--atproto-color-border: #d6dce3;
17
+
--atproto-color-border-subtle: #c1cad4;
18
+
--atproto-color-border-hover: #94a3b8;
19
--atproto-color-link: #2563eb;
20
--atproto-color-link-hover: #1d4ed8;
21
--atproto-color-error: #dc2626;
22
+
--atproto-color-primary: #2563eb;
23
+
--atproto-color-button-bg: #edf1f5;
24
+
--atproto-color-button-hover: #e3e9ef;
25
--atproto-color-button-text: #0f172a;
26
+
--atproto-color-bg-hover: #f0f3f6;
27
+
--atproto-color-bg-pressed: #e3e9ef;
28
+
--atproto-color-code-bg: #edf1f5;
29
+
--atproto-color-code-border: #d6dce3;
30
+
--atproto-color-blockquote-border: #c1cad4;
31
+
--atproto-color-blockquote-bg: #f0f3f6;
32
+
--atproto-color-hr: #d6dce3;
33
+
--atproto-color-image-bg: #edf1f5;
34
--atproto-color-highlight: #fef08a;
35
}
36
37
/* Dark theme - can be applied via [data-theme="dark"] or .dark class */
38
[data-theme="dark"],
39
.dark {
40
+
--atproto-color-bg: #141b22;
41
+
--atproto-color-bg-elevated: #1a222a;
42
+
--atproto-color-bg-secondary: #0f161c;
43
+
--atproto-color-text: #fafafa;
44
+
--atproto-color-text-secondary: #a1a1aa;
45
+
--atproto-color-text-muted: #71717a;
46
+
--atproto-color-border: #1f2933;
47
+
--atproto-color-border-subtle: #2d3748;
48
+
--atproto-color-border-hover: #4a5568;
49
--atproto-color-link: #60a5fa;
50
--atproto-color-link-hover: #93c5fd;
51
--atproto-color-error: #ef4444;
52
+
--atproto-color-primary: #3b82f6;
53
+
--atproto-color-button-bg: #1a222a;
54
+
--atproto-color-button-hover: #243039;
55
+
--atproto-color-button-text: #fafafa;
56
+
--atproto-color-bg-hover: #1a222a;
57
+
--atproto-color-bg-pressed: #243039;
58
+
--atproto-color-code-bg: #0f161c;
59
+
--atproto-color-code-border: #1f2933;
60
+
--atproto-color-blockquote-border: #2d3748;
61
+
--atproto-color-blockquote-bg: #1a222a;
62
+
--atproto-color-hr: #243039;
63
+
--atproto-color-image-bg: #1a222a;
64
--atproto-color-highlight: #854d0e;
65
}
66
···
68
@media (prefers-color-scheme: dark) {
69
:root:not([data-theme]),
70
:root[data-theme="system"] {
71
+
--atproto-color-bg: #141b22;
72
+
--atproto-color-bg-elevated: #1a222a;
73
+
--atproto-color-bg-secondary: #0f161c;
74
+
--atproto-color-text: #fafafa;
75
+
--atproto-color-text-secondary: #a1a1aa;
76
+
--atproto-color-text-muted: #71717a;
77
+
--atproto-color-border: #1f2933;
78
+
--atproto-color-border-subtle: #2d3748;
79
+
--atproto-color-border-hover: #4a5568;
80
--atproto-color-link: #60a5fa;
81
--atproto-color-link-hover: #93c5fd;
82
--atproto-color-error: #ef4444;
83
+
--atproto-color-primary: #3b82f6;
84
+
--atproto-color-button-bg: #1a222a;
85
+
--atproto-color-button-hover: #243039;
86
+
--atproto-color-button-text: #fafafa;
87
+
--atproto-color-bg-hover: #1a222a;
88
+
--atproto-color-bg-pressed: #243039;
89
+
--atproto-color-code-bg: #0f161c;
90
+
--atproto-color-code-border: #1f2933;
91
+
--atproto-color-blockquote-border: #2d3748;
92
+
--atproto-color-blockquote-bg: #1a222a;
93
+
--atproto-color-hr: #243039;
94
+
--atproto-color-image-bg: #1a222a;
95
--atproto-color-highlight: #854d0e;
96
}
97
}
+95
lib/types/grain.ts
+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
+
}
+37
-4
lib/utils/atproto-client.ts
+37
-4
lib/utils/atproto-client.ts
···
13
export interface ServiceResolverOptions {
14
plcDirectory?: string;
15
identityService?: string;
16
fetch?: typeof fetch;
17
}
18
19
const DEFAULT_PLC = "https://plc.directory";
20
const DEFAULT_IDENTITY_SERVICE = "https://public.api.bsky.app";
21
const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
22
const SUPPORTED_DID_METHODS = ["plc", "web"] as const;
23
type SupportedDidMethod = (typeof SUPPORTED_DID_METHODS)[number];
24
type SupportedDid = Did<SupportedDidMethod>;
25
26
-
export const SLINGSHOT_BASE_URL = "https://slingshot.microcosm.blue";
27
28
export const normalizeBaseUrl = (input: string): string => {
29
const trimmed = input.trim();
···
38
39
export class ServiceResolver {
40
private plc: string;
41
private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>;
42
private handleResolver: XrpcHandleResolver;
43
private fetchImpl: typeof fetch;
···
50
opts.identityService && opts.identityService.trim()
51
? opts.identityService
52
: DEFAULT_IDENTITY_SERVICE;
53
this.plc = normalizeBaseUrl(plcSource);
54
const identityBase = normalizeBaseUrl(identitySource);
55
this.fetchImpl = bindFetch(opts.fetch);
56
const plcResolver = new PlcDidDocumentResolver({
57
apiUrl: this.plc,
···
97
return svc.serviceEndpoint.replace(/\/$/, "");
98
}
99
100
async resolveHandle(handle: string): Promise<string> {
101
const normalized = handle.trim().toLowerCase();
102
if (!normalized) throw new Error("Handle cannot be empty");
···
104
try {
105
const url = new URL(
106
"/xrpc/com.atproto.identity.resolveHandle",
107
-
SLINGSHOT_BASE_URL,
108
);
109
url.searchParams.set("handle", normalized);
110
const response = await this.fetchImpl(url);
···
161
}
162
if (!service) throw new Error("service or did required");
163
const normalizedService = normalizeBaseUrl(service);
164
-
const handler = createSlingshotAwareHandler(normalizedService, fetchImpl);
165
const rpc = new Client({ handler });
166
return { rpc, service: normalizedService, resolver };
167
}
···
177
178
function createSlingshotAwareHandler(
179
service: string,
180
fetchImpl: typeof fetch,
181
): FetchHandler {
182
const primary = simpleFetchHandler({ service, fetch: fetchImpl });
183
const slingshot = simpleFetchHandler({
184
-
service: SLINGSHOT_BASE_URL,
185
fetch: fetchImpl,
186
});
187
return async (pathname, init) => {
···
13
export interface ServiceResolverOptions {
14
plcDirectory?: string;
15
identityService?: string;
16
+
slingshotBaseUrl?: string;
17
fetch?: typeof fetch;
18
}
19
20
const DEFAULT_PLC = "https://plc.directory";
21
const DEFAULT_IDENTITY_SERVICE = "https://public.api.bsky.app";
22
+
const DEFAULT_SLINGSHOT = "https://slingshot.microcosm.blue";
23
+
const DEFAULT_APPVIEW = "https://public.api.bsky.app";
24
+
const DEFAULT_BLUESKY_APP = "https://bsky.app";
25
+
const DEFAULT_TANGLED = "https://tangled.org";
26
+
const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue";
27
+
28
const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
29
const SUPPORTED_DID_METHODS = ["plc", "web"] as const;
30
type SupportedDidMethod = (typeof SUPPORTED_DID_METHODS)[number];
31
type SupportedDid = Did<SupportedDidMethod>;
32
33
+
/**
34
+
* Default configuration values for AT Protocol services.
35
+
* These can be overridden via AtProtoProvider props.
36
+
*/
37
+
export const DEFAULT_CONFIG = {
38
+
plcDirectory: DEFAULT_PLC,
39
+
identityService: DEFAULT_IDENTITY_SERVICE,
40
+
slingshotBaseUrl: DEFAULT_SLINGSHOT,
41
+
blueskyAppviewService: DEFAULT_APPVIEW,
42
+
blueskyAppBaseUrl: DEFAULT_BLUESKY_APP,
43
+
tangledBaseUrl: DEFAULT_TANGLED,
44
+
constellationBaseUrl: DEFAULT_CONSTELLATION,
45
+
} as const;
46
+
47
+
export const SLINGSHOT_BASE_URL = DEFAULT_SLINGSHOT;
48
49
export const normalizeBaseUrl = (input: string): string => {
50
const trimmed = input.trim();
···
59
60
export class ServiceResolver {
61
private plc: string;
62
+
private slingshot: string;
63
private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>;
64
private handleResolver: XrpcHandleResolver;
65
private fetchImpl: typeof fetch;
···
72
opts.identityService && opts.identityService.trim()
73
? opts.identityService
74
: DEFAULT_IDENTITY_SERVICE;
75
+
const slingshotSource =
76
+
opts.slingshotBaseUrl && opts.slingshotBaseUrl.trim()
77
+
? opts.slingshotBaseUrl
78
+
: DEFAULT_SLINGSHOT;
79
this.plc = normalizeBaseUrl(plcSource);
80
const identityBase = normalizeBaseUrl(identitySource);
81
+
this.slingshot = normalizeBaseUrl(slingshotSource);
82
this.fetchImpl = bindFetch(opts.fetch);
83
const plcResolver = new PlcDidDocumentResolver({
84
apiUrl: this.plc,
···
124
return svc.serviceEndpoint.replace(/\/$/, "");
125
}
126
127
+
getSlingshotUrl(): string {
128
+
return this.slingshot;
129
+
}
130
+
131
async resolveHandle(handle: string): Promise<string> {
132
const normalized = handle.trim().toLowerCase();
133
if (!normalized) throw new Error("Handle cannot be empty");
···
135
try {
136
const url = new URL(
137
"/xrpc/com.atproto.identity.resolveHandle",
138
+
this.slingshot,
139
);
140
url.searchParams.set("handle", normalized);
141
const response = await this.fetchImpl(url);
···
192
}
193
if (!service) throw new Error("service or did required");
194
const normalizedService = normalizeBaseUrl(service);
195
+
const slingshotUrl = resolver.getSlingshotUrl();
196
+
const handler = createSlingshotAwareHandler(normalizedService, slingshotUrl, fetchImpl);
197
const rpc = new Client({ handler });
198
return { rpc, service: normalizedService, resolver };
199
}
···
209
210
function createSlingshotAwareHandler(
211
service: string,
212
+
slingshotBaseUrl: string,
213
fetchImpl: typeof fetch,
214
): FetchHandler {
215
const primary = simpleFetchHandler({ service, fetch: fetchImpl });
216
const slingshot = simpleFetchHandler({
217
+
service: slingshotBaseUrl,
218
fetch: fetchImpl,
219
});
220
return async (pathname, init) => {
+120
lib/utils/cache.ts
+120
lib/utils/cache.ts
···
270
}
271
}
272
}
273
+
274
+
interface RecordCacheEntry<T = unknown> {
275
+
record: T;
276
+
timestamp: number;
277
+
}
278
+
279
+
interface InFlightRecordEntry<T = unknown> {
280
+
promise: Promise<T>;
281
+
abort: () => void;
282
+
refCount: number;
283
+
}
284
+
285
+
interface RecordEnsureResult<T = unknown> {
286
+
promise: Promise<T>;
287
+
release: () => void;
288
+
}
289
+
290
+
export class RecordCache {
291
+
private store = new Map<string, RecordCacheEntry>();
292
+
private inFlight = new Map<string, InFlightRecordEntry>();
293
+
// Collections that should not be cached (e.g., status records that change frequently)
294
+
private noCacheCollections = new Set<string>([
295
+
"fm.teal.alpha.actor.status",
296
+
"fm.teal.alpha.feed.play",
297
+
]);
298
+
299
+
private key(did: string, collection: string, rkey: string): string {
300
+
return `${did}::${collection}::${rkey}`;
301
+
}
302
+
303
+
private shouldCache(collection: string): boolean {
304
+
return !this.noCacheCollections.has(collection);
305
+
}
306
+
307
+
get<T = unknown>(
308
+
did?: string,
309
+
collection?: string,
310
+
rkey?: string,
311
+
): T | undefined {
312
+
if (!did || !collection || !rkey) return undefined;
313
+
// Don't return cached data for non-cacheable collections
314
+
if (!this.shouldCache(collection)) return undefined;
315
+
return this.store.get(this.key(did, collection, rkey))?.record as
316
+
| T
317
+
| undefined;
318
+
}
319
+
320
+
set<T = unknown>(
321
+
did: string,
322
+
collection: string,
323
+
rkey: string,
324
+
record: T,
325
+
): void {
326
+
// Don't cache records for non-cacheable collections
327
+
if (!this.shouldCache(collection)) return;
328
+
this.store.set(this.key(did, collection, rkey), {
329
+
record,
330
+
timestamp: Date.now(),
331
+
});
332
+
}
333
+
334
+
ensure<T = unknown>(
335
+
did: string,
336
+
collection: string,
337
+
rkey: string,
338
+
loader: () => { promise: Promise<T>; abort: () => void },
339
+
): RecordEnsureResult<T> {
340
+
const cached = this.get<T>(did, collection, rkey);
341
+
if (cached !== undefined) {
342
+
return { promise: Promise.resolve(cached), release: () => {} };
343
+
}
344
+
345
+
const key = this.key(did, collection, rkey);
346
+
const existing = this.inFlight.get(key) as
347
+
| InFlightRecordEntry<T>
348
+
| undefined;
349
+
if (existing) {
350
+
existing.refCount += 1;
351
+
return {
352
+
promise: existing.promise,
353
+
release: () => this.release(key),
354
+
};
355
+
}
356
+
357
+
const { promise, abort } = loader();
358
+
const wrapped = promise.then((record) => {
359
+
this.set(did, collection, rkey, record);
360
+
return record;
361
+
});
362
+
363
+
const entry: InFlightRecordEntry<T> = {
364
+
promise: wrapped,
365
+
abort,
366
+
refCount: 1,
367
+
};
368
+
369
+
this.inFlight.set(key, entry as InFlightRecordEntry);
370
+
371
+
wrapped
372
+
.catch(() => {})
373
+
.finally(() => {
374
+
this.inFlight.delete(key);
375
+
});
376
+
377
+
return {
378
+
promise: wrapped,
379
+
release: () => this.release(key),
380
+
};
381
+
}
382
+
383
+
private release(key: string) {
384
+
const entry = this.inFlight.get(key);
385
+
if (!entry) return;
386
+
entry.refCount -= 1;
387
+
if (entry.refCount <= 0) {
388
+
this.inFlight.delete(key);
389
+
entry.abort();
390
+
}
391
+
}
392
+
}
+1219
-1234
package-lock.json
+1219
-1234
package-lock.json
···
1
{
2
"name": "atproto-ui",
3
-
"version": "0.5.2-beta",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "atproto-ui",
9
-
"version": "0.5.2-beta",
10
"dependencies": {
11
"@atcute/atproto": "^3.1.7",
12
"@atcute/bluesky": "^3.2.3",
13
"@atcute/client": "^4.0.3",
14
"@atcute/identity-resolver": "^1.1.3",
15
-
"@atcute/tangled": "^1.0.6"
16
},
17
"devDependencies": {
18
"@eslint/js": "^9.36.0",
···
44
}
45
},
46
"node_modules/@atcute/atproto": {
47
-
"version": "3.1.7",
48
-
"resolved": "https://registry.npmjs.org/@atcute/atproto/-/atproto-3.1.7.tgz",
49
-
"integrity": "sha512-3Ym8qaVZg2vf8qw0KO1aue39z/5oik5J+UDoSes1vr8ddw40UVLA5sV4bXSKmLnhzQHiLLgoVZXe4zaKfozPoQ==",
50
"license": "0BSD",
51
"dependencies": {
52
"@atcute/lexicons": "^1.2.2"
53
}
54
},
55
"node_modules/@atcute/bluesky": {
56
-
"version": "3.2.3",
57
-
"resolved": "https://registry.npmjs.org/@atcute/bluesky/-/bluesky-3.2.3.tgz",
58
-
"integrity": "sha512-IdPQQ54F1BLhW5z49k81ZUC/GQl/tVygZ+CzLHYvQySHA6GJRcvPzwEf8aV21u0SZOJF+yF4CWEGNgtryyxPmg==",
59
"license": "0BSD",
60
"dependencies": {
61
-
"@atcute/atproto": "^3.1.4",
62
-
"@atcute/lexicons": "^1.1.1"
63
}
64
},
65
"node_modules/@atcute/client": {
66
-
"version": "4.0.3",
67
-
"resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz",
68
-
"integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==",
69
-
"license": "MIT",
70
"dependencies": {
71
-
"@atcute/identity": "^1.0.2",
72
-
"@atcute/lexicons": "^1.0.3"
73
}
74
},
75
"node_modules/@atcute/identity": {
76
-
"version": "1.1.0",
77
-
"resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.0.tgz",
78
-
"integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==",
79
"license": "0BSD",
80
"dependencies": {
81
-
"@atcute/lexicons": "^1.1.1",
82
-
"@badrap/valita": "^0.4.5"
83
}
84
},
85
"node_modules/@atcute/identity-resolver": {
86
"version": "1.1.4",
87
-
"resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.4.tgz",
88
-
"integrity": "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==",
89
"license": "0BSD",
90
"dependencies": {
91
"@atcute/lexicons": "^1.2.2",
···
97
}
98
},
99
"node_modules/@atcute/lexicons": {
100
-
"version": "1.2.2",
101
-
"resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.2.2.tgz",
102
-
"integrity": "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==",
103
"license": "0BSD",
104
"dependencies": {
105
"@standard-schema/spec": "^1.0.0",
···
107
}
108
},
109
"node_modules/@atcute/tangled": {
110
-
"version": "1.0.6",
111
-
"resolved": "https://registry.npmjs.org/@atcute/tangled/-/tangled-1.0.6.tgz",
112
-
"integrity": "sha512-eEOtrKRbjKfeLYtb5hmkhE45w8h4sV6mT4E2CQzJmhOMGCiK31GX7Vqfh59rhNLb9AlbW72RcQTV737pxx+ksw==",
113
"license": "0BSD",
114
"dependencies": {
115
-
"@atcute/atproto": "^3.1.4",
116
-
"@atcute/lexicons": "^1.1.1"
117
}
118
},
119
"node_modules/@atcute/util-fetch": {
120
-
"version": "1.0.3",
121
-
"resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.3.tgz",
122
-
"integrity": "sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==",
123
"license": "0BSD",
124
"dependencies": {
125
"@badrap/valita": "^0.4.6"
···
127
},
128
"node_modules/@babel/code-frame": {
129
"version": "7.27.1",
130
-
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
131
-
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
132
"dev": true,
133
"license": "MIT",
134
"dependencies": {
···
141
}
142
},
143
"node_modules/@babel/compat-data": {
144
-
"version": "7.28.4",
145
-
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
146
-
"integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
147
"dev": true,
148
"license": "MIT",
149
"engines": {
···
151
}
152
},
153
"node_modules/@babel/core": {
154
-
"version": "7.28.4",
155
-
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
156
-
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
157
"dev": true,
158
"license": "MIT",
159
"dependencies": {
160
"@babel/code-frame": "^7.27.1",
161
-
"@babel/generator": "^7.28.3",
162
"@babel/helper-compilation-targets": "^7.27.2",
163
"@babel/helper-module-transforms": "^7.28.3",
164
"@babel/helpers": "^7.28.4",
165
-
"@babel/parser": "^7.28.4",
166
"@babel/template": "^7.27.2",
167
-
"@babel/traverse": "^7.28.4",
168
-
"@babel/types": "^7.28.4",
169
"@jridgewell/remapping": "^2.3.5",
170
"convert-source-map": "^2.0.0",
171
"debug": "^4.1.0",
···
181
"url": "https://opencollective.com/babel"
182
}
183
},
184
"node_modules/@babel/generator": {
185
-
"version": "7.28.3",
186
-
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
187
-
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
188
"dev": true,
189
"license": "MIT",
190
"dependencies": {
191
-
"@babel/parser": "^7.28.3",
192
-
"@babel/types": "^7.28.2",
193
"@jridgewell/gen-mapping": "^0.3.12",
194
"@jridgewell/trace-mapping": "^0.3.28",
195
"jsesc": "^3.0.2"
···
200
},
201
"node_modules/@babel/helper-compilation-targets": {
202
"version": "7.27.2",
203
-
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
204
-
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
205
"dev": true,
206
"license": "MIT",
207
"dependencies": {
···
215
"node": ">=6.9.0"
216
}
217
},
218
"node_modules/@babel/helper-globals": {
219
"version": "7.28.0",
220
-
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
221
-
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
222
"dev": true,
223
"license": "MIT",
224
"engines": {
···
227
},
228
"node_modules/@babel/helper-module-imports": {
229
"version": "7.27.1",
230
-
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
231
-
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
232
"dev": true,
233
"license": "MIT",
234
"dependencies": {
···
241
},
242
"node_modules/@babel/helper-module-transforms": {
243
"version": "7.28.3",
244
-
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
245
-
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
246
"dev": true,
247
"license": "MIT",
248
"dependencies": {
···
259
},
260
"node_modules/@babel/helper-plugin-utils": {
261
"version": "7.27.1",
262
-
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
263
-
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
264
"dev": true,
265
"license": "MIT",
266
"engines": {
···
269
},
270
"node_modules/@babel/helper-string-parser": {
271
"version": "7.27.1",
272
-
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
273
-
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
274
"dev": true,
275
"license": "MIT",
276
"engines": {
···
278
}
279
},
280
"node_modules/@babel/helper-validator-identifier": {
281
-
"version": "7.27.1",
282
-
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
283
-
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
284
"dev": true,
285
"license": "MIT",
286
"engines": {
···
289
},
290
"node_modules/@babel/helper-validator-option": {
291
"version": "7.27.1",
292
-
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
293
-
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
294
"dev": true,
295
"license": "MIT",
296
"engines": {
···
299
},
300
"node_modules/@babel/helpers": {
301
"version": "7.28.4",
302
-
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
303
-
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
304
"dev": true,
305
"license": "MIT",
306
"dependencies": {
···
312
}
313
},
314
"node_modules/@babel/parser": {
315
-
"version": "7.28.4",
316
-
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
317
-
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
318
"dev": true,
319
"license": "MIT",
320
"dependencies": {
321
-
"@babel/types": "^7.28.4"
322
},
323
"bin": {
324
"parser": "bin/babel-parser.js"
···
329
},
330
"node_modules/@babel/plugin-transform-react-jsx-self": {
331
"version": "7.27.1",
332
-
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
333
-
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
334
"dev": true,
335
"license": "MIT",
336
"dependencies": {
···
345
},
346
"node_modules/@babel/plugin-transform-react-jsx-source": {
347
"version": "7.27.1",
348
-
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
349
-
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
350
"dev": true,
351
"license": "MIT",
352
"dependencies": {
···
361
},
362
"node_modules/@babel/template": {
363
"version": "7.27.2",
364
-
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
365
-
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
366
"dev": true,
367
"license": "MIT",
368
"dependencies": {
···
375
}
376
},
377
"node_modules/@babel/traverse": {
378
-
"version": "7.28.4",
379
-
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
380
-
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
381
"dev": true,
382
"license": "MIT",
383
"dependencies": {
384
"@babel/code-frame": "^7.27.1",
385
-
"@babel/generator": "^7.28.3",
386
"@babel/helper-globals": "^7.28.0",
387
-
"@babel/parser": "^7.28.4",
388
"@babel/template": "^7.27.2",
389
-
"@babel/types": "^7.28.4",
390
"debug": "^4.3.1"
391
},
392
"engines": {
···
394
}
395
},
396
"node_modules/@babel/types": {
397
-
"version": "7.28.4",
398
-
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
399
-
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
400
"dev": true,
401
"license": "MIT",
402
"dependencies": {
403
"@babel/helper-string-parser": "^7.27.1",
404
-
"@babel/helper-validator-identifier": "^7.27.1"
405
},
406
"engines": {
407
"node": ">=6.9.0"
···
409
},
410
"node_modules/@badrap/valita": {
411
"version": "0.4.6",
412
-
"resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz",
413
-
"integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==",
414
"license": "MIT",
415
"engines": {
416
"node": ">= 18"
417
}
418
},
419
"node_modules/@eslint-community/eslint-utils": {
420
"version": "4.9.0",
421
-
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
422
-
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
423
"dev": true,
424
"license": "MIT",
425
"dependencies": {
···
437
},
438
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
439
"version": "3.4.3",
440
-
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
441
-
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
442
"dev": true,
443
"license": "Apache-2.0",
444
"engines": {
···
449
}
450
},
451
"node_modules/@eslint-community/regexpp": {
452
-
"version": "4.12.1",
453
-
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
454
-
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
455
"dev": true,
456
"license": "MIT",
457
"engines": {
···
459
}
460
},
461
"node_modules/@eslint/config-array": {
462
-
"version": "0.21.0",
463
-
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
464
-
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
465
"dev": true,
466
"license": "Apache-2.0",
467
"dependencies": {
468
-
"@eslint/object-schema": "^2.1.6",
469
"debug": "^4.3.1",
470
"minimatch": "^3.1.2"
471
},
···
473
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
474
}
475
},
476
"node_modules/@eslint/config-helpers": {
477
-
"version": "0.4.0",
478
-
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
479
-
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
480
"dev": true,
481
"license": "Apache-2.0",
482
"dependencies": {
483
-
"@eslint/core": "^0.16.0"
484
},
485
"engines": {
486
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
487
}
488
},
489
"node_modules/@eslint/core": {
490
-
"version": "0.16.0",
491
-
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
492
-
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
493
"dev": true,
494
"license": "Apache-2.0",
495
"dependencies": {
···
500
}
501
},
502
"node_modules/@eslint/eslintrc": {
503
-
"version": "3.3.1",
504
-
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
505
-
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
506
"dev": true,
507
"license": "MIT",
508
"dependencies": {
···
512
"globals": "^14.0.0",
513
"ignore": "^5.2.0",
514
"import-fresh": "^3.2.1",
515
-
"js-yaml": "^4.1.0",
516
"minimatch": "^3.1.2",
517
"strip-json-comments": "^3.1.1"
518
},
···
523
"url": "https://opencollective.com/eslint"
524
}
525
},
526
"node_modules/@eslint/eslintrc/node_modules/globals": {
527
"version": "14.0.0",
528
-
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
529
-
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
530
"dev": true,
531
"license": "MIT",
532
"engines": {
···
536
"url": "https://github.com/sponsors/sindresorhus"
537
}
538
},
539
"node_modules/@eslint/js": {
540
-
"version": "9.37.0",
541
-
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
542
-
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
543
"dev": true,
544
"license": "MIT",
545
"engines": {
···
550
}
551
},
552
"node_modules/@eslint/object-schema": {
553
-
"version": "2.1.6",
554
-
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
555
-
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
556
"dev": true,
557
"license": "Apache-2.0",
558
"engines": {
···
560
}
561
},
562
"node_modules/@eslint/plugin-kit": {
563
-
"version": "0.4.0",
564
-
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
565
-
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
566
"dev": true,
567
"license": "Apache-2.0",
568
"dependencies": {
569
-
"@eslint/core": "^0.16.0",
570
"levn": "^0.4.1"
571
},
572
"engines": {
···
575
},
576
"node_modules/@humanfs/core": {
577
"version": "0.19.1",
578
-
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
579
-
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
580
"dev": true,
581
"license": "Apache-2.0",
582
"engines": {
···
585
},
586
"node_modules/@humanfs/node": {
587
"version": "0.16.7",
588
-
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
589
-
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
590
"dev": true,
591
"license": "Apache-2.0",
592
"dependencies": {
···
599
},
600
"node_modules/@humanwhocodes/module-importer": {
601
"version": "1.0.1",
602
-
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
603
-
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
604
"dev": true,
605
"license": "Apache-2.0",
606
"engines": {
···
613
},
614
"node_modules/@humanwhocodes/retry": {
615
"version": "0.4.3",
616
-
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
617
-
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
618
"dev": true,
619
"license": "Apache-2.0",
620
"engines": {
···
627
},
628
"node_modules/@isaacs/balanced-match": {
629
"version": "4.0.1",
630
-
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
631
-
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
632
"dev": true,
633
"license": "MIT",
634
"engines": {
···
637
},
638
"node_modules/@isaacs/brace-expansion": {
639
"version": "5.0.0",
640
-
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
641
-
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
642
"dev": true,
643
"license": "MIT",
644
"dependencies": {
···
650
},
651
"node_modules/@jridgewell/gen-mapping": {
652
"version": "0.3.13",
653
-
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
654
-
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
655
"dev": true,
656
"license": "MIT",
657
"dependencies": {
···
661
},
662
"node_modules/@jridgewell/remapping": {
663
"version": "2.3.5",
664
-
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
665
-
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
666
"dev": true,
667
"license": "MIT",
668
"dependencies": {
···
672
},
673
"node_modules/@jridgewell/resolve-uri": {
674
"version": "3.1.2",
675
-
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
676
-
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
677
"dev": true,
678
"license": "MIT",
679
"engines": {
···
682
},
683
"node_modules/@jridgewell/source-map": {
684
"version": "0.3.11",
685
-
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
686
-
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
687
"dev": true,
688
"license": "MIT",
689
"optional": true,
690
-
"peer": true,
691
"dependencies": {
692
"@jridgewell/gen-mapping": "^0.3.5",
693
"@jridgewell/trace-mapping": "^0.3.25"
···
695
},
696
"node_modules/@jridgewell/sourcemap-codec": {
697
"version": "1.5.5",
698
-
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
699
-
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
700
"dev": true,
701
"license": "MIT"
702
},
703
"node_modules/@jridgewell/trace-mapping": {
704
"version": "0.3.31",
705
-
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
706
-
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
707
"dev": true,
708
"license": "MIT",
709
"dependencies": {
···
712
}
713
},
714
"node_modules/@microsoft/api-extractor": {
715
-
"version": "7.53.1",
716
-
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.53.1.tgz",
717
-
"integrity": "sha512-bul5eTNxijLdDBqLye74u9494sRmf+9QULtec9Od0uHnifahGeNt8CC4/xCdn7mVyEBrXIQyQ5+sc4Uc0QfBSA==",
718
"dev": true,
719
"license": "MIT",
720
"dependencies": {
721
-
"@microsoft/api-extractor-model": "7.31.1",
722
-
"@microsoft/tsdoc": "~0.15.1",
723
-
"@microsoft/tsdoc-config": "~0.17.1",
724
-
"@rushstack/node-core-library": "5.17.0",
725
"@rushstack/rig-package": "0.6.0",
726
-
"@rushstack/terminal": "0.19.1",
727
-
"@rushstack/ts-command-line": "5.1.1",
728
"lodash": "~4.17.15",
729
"minimatch": "10.0.3",
730
"resolve": "~1.22.1",
···
737
}
738
},
739
"node_modules/@microsoft/api-extractor-model": {
740
-
"version": "7.31.1",
741
-
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.31.1.tgz",
742
-
"integrity": "sha512-Dhnip5OFKbl85rq/ICHBFGhV4RA5UQSl8AC/P/zoGvs+CBudPkatt5kIhMGiYgVPnUWmfR6fcp38+1AFLYNtUw==",
743
"dev": true,
744
"license": "MIT",
745
"dependencies": {
746
-
"@microsoft/tsdoc": "~0.15.1",
747
-
"@microsoft/tsdoc-config": "~0.17.1",
748
-
"@rushstack/node-core-library": "5.17.0"
749
-
}
750
-
},
751
-
"node_modules/@microsoft/api-extractor/node_modules/lru-cache": {
752
-
"version": "6.0.0",
753
-
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
754
-
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
755
-
"dev": true,
756
-
"license": "ISC",
757
-
"dependencies": {
758
-
"yallist": "^4.0.0"
759
-
},
760
-
"engines": {
761
-
"node": ">=10"
762
-
}
763
-
},
764
-
"node_modules/@microsoft/api-extractor/node_modules/minimatch": {
765
-
"version": "10.0.3",
766
-
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
767
-
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
768
-
"dev": true,
769
-
"license": "ISC",
770
-
"dependencies": {
771
-
"@isaacs/brace-expansion": "^5.0.0"
772
-
},
773
-
"engines": {
774
-
"node": "20 || >=22"
775
-
},
776
-
"funding": {
777
-
"url": "https://github.com/sponsors/isaacs"
778
-
}
779
-
},
780
-
"node_modules/@microsoft/api-extractor/node_modules/semver": {
781
-
"version": "7.5.4",
782
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
783
-
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
784
-
"dev": true,
785
-
"license": "ISC",
786
-
"dependencies": {
787
-
"lru-cache": "^6.0.0"
788
-
},
789
-
"bin": {
790
-
"semver": "bin/semver.js"
791
-
},
792
-
"engines": {
793
-
"node": ">=10"
794
}
795
},
796
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
797
"version": "5.8.2",
798
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
799
-
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
800
"dev": true,
801
"license": "Apache-2.0",
802
"bin": {
···
807
"node": ">=14.17"
808
}
809
},
810
-
"node_modules/@microsoft/api-extractor/node_modules/yallist": {
811
-
"version": "4.0.0",
812
-
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
813
-
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
814
-
"dev": true,
815
-
"license": "ISC"
816
-
},
817
"node_modules/@microsoft/tsdoc": {
818
-
"version": "0.15.1",
819
-
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
820
-
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
821
"dev": true,
822
"license": "MIT"
823
},
824
"node_modules/@microsoft/tsdoc-config": {
825
-
"version": "0.17.1",
826
-
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz",
827
-
"integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==",
828
"dev": true,
829
"license": "MIT",
830
"dependencies": {
831
-
"@microsoft/tsdoc": "0.15.1",
832
"ajv": "~8.12.0",
833
"jju": "~1.4.0",
834
"resolve": "~1.22.2"
···
836
},
837
"node_modules/@microsoft/tsdoc-config/node_modules/ajv": {
838
"version": "8.12.0",
839
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
840
-
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
841
"dev": true,
842
"license": "MIT",
843
"dependencies": {
···
851
"url": "https://github.com/sponsors/epoberezkin"
852
}
853
},
854
-
"node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": {
855
-
"version": "1.0.0",
856
-
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
857
-
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
858
"dev": true,
859
-
"license": "MIT"
860
},
861
-
"node_modules/@nodelib/fs.scandir": {
862
-
"version": "2.1.5",
863
-
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
864
-
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
865
"dev": true,
866
"license": "MIT",
867
-
"dependencies": {
868
-
"@nodelib/fs.stat": "2.0.5",
869
-
"run-parallel": "^1.1.9"
870
-
},
871
"engines": {
872
-
"node": ">= 8"
873
}
874
},
875
-
"node_modules/@nodelib/fs.stat": {
876
-
"version": "2.0.5",
877
-
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
878
-
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
879
"dev": true,
880
"license": "MIT",
881
"engines": {
882
-
"node": ">= 8"
883
}
884
},
885
-
"node_modules/@nodelib/fs.walk": {
886
-
"version": "1.2.8",
887
-
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
888
-
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
889
"dev": true,
890
"license": "MIT",
891
"dependencies": {
892
-
"@nodelib/fs.scandir": "2.1.5",
893
-
"fastq": "^1.6.0"
894
},
895
"engines": {
896
-
"node": ">= 8"
897
}
898
},
899
-
"node_modules/@oxc-project/runtime": {
900
-
"version": "0.92.0",
901
-
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.92.0.tgz",
902
-
"integrity": "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==",
903
"dev": true,
904
"license": "MIT",
905
"engines": {
906
"node": "^20.19.0 || >=22.12.0"
907
}
908
},
909
-
"node_modules/@oxc-project/types": {
910
-
"version": "0.93.0",
911
-
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.93.0.tgz",
912
-
"integrity": "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==",
913
"dev": true,
914
"license": "MIT",
915
-
"funding": {
916
-
"url": "https://github.com/sponsors/Boshen"
917
}
918
},
919
-
"node_modules/@rolldown/binding-darwin-arm64": {
920
"version": "1.0.0-beta.41",
921
-
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz",
922
-
"integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==",
923
"cpu": [
924
-
"arm64"
925
],
926
"dev": true,
927
"license": "MIT",
928
"optional": true,
929
"os": [
930
-
"darwin"
931
],
932
"engines": {
933
"node": "^20.19.0 || >=22.12.0"
934
}
935
},
936
"node_modules/@rolldown/pluginutils": {
937
-
"version": "1.0.0-beta.38",
938
-
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz",
939
-
"integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==",
940
"dev": true,
941
"license": "MIT"
942
},
943
"node_modules/@rollup/pluginutils": {
944
-
"version": "5.3.0",
945
-
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
946
-
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
947
"dev": true,
948
"license": "MIT",
949
"dependencies": {
950
-
"@types/estree": "^1.0.0",
951
-
"estree-walker": "^2.0.2",
952
-
"picomatch": "^4.0.2"
953
},
954
"engines": {
955
-
"node": ">=14.0.0"
956
-
},
957
-
"peerDependencies": {
958
-
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
959
-
},
960
-
"peerDependenciesMeta": {
961
-
"rollup": {
962
-
"optional": true
963
-
}
964
}
965
},
966
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
967
-
"version": "4.0.3",
968
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
969
-
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
970
"dev": true,
971
"license": "MIT",
972
"engines": {
973
-
"node": ">=12"
974
},
975
"funding": {
976
"url": "https://github.com/sponsors/jonschlinkert"
977
}
978
},
979
"node_modules/@rollup/rollup-darwin-arm64": {
980
-
"version": "4.52.4",
981
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
982
-
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
983
"cpu": [
984
"arm64"
985
],
···
988
"optional": true,
989
"os": [
990
"darwin"
991
],
992
-
"peer": true
993
},
994
"node_modules/@rushstack/node-core-library": {
995
-
"version": "5.17.0",
996
-
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.17.0.tgz",
997
-
"integrity": "sha512-24vt1GbHN6kyIglRMTVpyEiNRRRJK8uZHc1XoGAhmnTDKnrWet8OmOpImMswJIe6gM78eV8cMg1HXwuUHkSSgg==",
998
"dev": true,
999
"license": "MIT",
1000
"dependencies": {
···
1018
},
1019
"node_modules/@rushstack/node-core-library/node_modules/ajv": {
1020
"version": "8.13.0",
1021
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
1022
-
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
1023
"dev": true,
1024
"license": "MIT",
1025
"dependencies": {
···
1033
"url": "https://github.com/sponsors/epoberezkin"
1034
}
1035
},
1036
-
"node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": {
1037
-
"version": "1.0.0",
1038
-
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
1039
-
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
1040
"dev": true,
1041
"license": "MIT",
1042
-
"peerDependencies": {
1043
-
"ajv": "^8.5.0"
1044
-
},
1045
-
"peerDependenciesMeta": {
1046
-
"ajv": {
1047
-
"optional": true
1048
-
}
1049
-
}
1050
-
},
1051
-
"node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": {
1052
-
"version": "1.0.0",
1053
-
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
1054
-
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
1055
-
"dev": true,
1056
-
"license": "MIT"
1057
-
},
1058
-
"node_modules/@rushstack/node-core-library/node_modules/lru-cache": {
1059
-
"version": "6.0.0",
1060
-
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
1061
-
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
1062
-
"dev": true,
1063
-
"license": "ISC",
1064
"dependencies": {
1065
-
"yallist": "^4.0.0"
1066
-
},
1067
-
"engines": {
1068
-
"node": ">=10"
1069
-
}
1070
-
},
1071
-
"node_modules/@rushstack/node-core-library/node_modules/semver": {
1072
-
"version": "7.5.4",
1073
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
1074
-
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
1075
-
"dev": true,
1076
-
"license": "ISC",
1077
-
"dependencies": {
1078
-
"lru-cache": "^6.0.0"
1079
-
},
1080
-
"bin": {
1081
-
"semver": "bin/semver.js"
1082
},
1083
"engines": {
1084
-
"node": ">=10"
1085
}
1086
},
1087
-
"node_modules/@rushstack/node-core-library/node_modules/yallist": {
1088
-
"version": "4.0.0",
1089
-
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1090
-
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
1091
-
"dev": true,
1092
-
"license": "ISC"
1093
-
},
1094
"node_modules/@rushstack/problem-matcher": {
1095
"version": "0.1.1",
1096
-
"resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz",
1097
-
"integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==",
1098
"dev": true,
1099
"license": "MIT",
1100
"peerDependencies": {
···
1108
},
1109
"node_modules/@rushstack/rig-package": {
1110
"version": "0.6.0",
1111
-
"resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz",
1112
-
"integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==",
1113
"dev": true,
1114
"license": "MIT",
1115
"dependencies": {
···
1118
}
1119
},
1120
"node_modules/@rushstack/terminal": {
1121
-
"version": "0.19.1",
1122
-
"resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.1.tgz",
1123
-
"integrity": "sha512-jsBuSad67IDVMO2yp0hDfs0OdE4z3mDIjIL2pclDT3aEJboeZXE85e1HjuD0F6JoW3XgHvDwoX+WOV+AVTDQeA==",
1124
"dev": true,
1125
"license": "MIT",
1126
"dependencies": {
1127
-
"@rushstack/node-core-library": "5.17.0",
1128
"@rushstack/problem-matcher": "0.1.1",
1129
"supports-color": "~8.1.1"
1130
},
···
1137
}
1138
}
1139
},
1140
-
"node_modules/@rushstack/terminal/node_modules/supports-color": {
1141
-
"version": "8.1.1",
1142
-
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
1143
-
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
1144
-
"dev": true,
1145
-
"license": "MIT",
1146
-
"dependencies": {
1147
-
"has-flag": "^4.0.0"
1148
-
},
1149
-
"engines": {
1150
-
"node": ">=10"
1151
-
},
1152
-
"funding": {
1153
-
"url": "https://github.com/chalk/supports-color?sponsor=1"
1154
-
}
1155
-
},
1156
"node_modules/@rushstack/ts-command-line": {
1157
-
"version": "5.1.1",
1158
-
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.1.tgz",
1159
-
"integrity": "sha512-HPzFsUcr+wZ3oQI08Ec/E6cuiAVHKzrXZGHhwiwIGygAFiqN5QzX+ff30n70NU2WyE26CykgMwBZZSSyHCJrzA==",
1160
"dev": true,
1161
"license": "MIT",
1162
"dependencies": {
1163
-
"@rushstack/terminal": "0.19.1",
1164
"@types/argparse": "1.0.38",
1165
"argparse": "~1.0.9",
1166
"string-argv": "~0.3.1"
1167
}
1168
},
1169
-
"node_modules/@rushstack/ts-command-line/node_modules/argparse": {
1170
-
"version": "1.0.10",
1171
-
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
1172
-
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
1173
"dev": true,
1174
"license": "MIT",
1175
"dependencies": {
1176
-
"sprintf-js": "~1.0.2"
1177
}
1178
},
1179
-
"node_modules/@standard-schema/spec": {
1180
-
"version": "1.0.0",
1181
-
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
1182
-
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
1183
-
"license": "MIT"
1184
-
},
1185
"node_modules/@types/argparse": {
1186
"version": "1.0.38",
1187
-
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
1188
-
"integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==",
1189
"dev": true,
1190
"license": "MIT"
1191
},
1192
"node_modules/@types/babel__core": {
1193
"version": "7.20.5",
1194
-
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1195
-
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1196
"dev": true,
1197
"license": "MIT",
1198
"dependencies": {
···
1205
},
1206
"node_modules/@types/babel__generator": {
1207
"version": "7.27.0",
1208
-
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1209
-
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1210
"dev": true,
1211
"license": "MIT",
1212
"dependencies": {
···
1215
},
1216
"node_modules/@types/babel__template": {
1217
"version": "7.4.4",
1218
-
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1219
-
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1220
"dev": true,
1221
"license": "MIT",
1222
"dependencies": {
···
1226
},
1227
"node_modules/@types/babel__traverse": {
1228
"version": "7.28.0",
1229
-
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1230
-
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1231
"dev": true,
1232
"license": "MIT",
1233
"dependencies": {
···
1236
},
1237
"node_modules/@types/estree": {
1238
"version": "1.0.8",
1239
-
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1240
-
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1241
"dev": true,
1242
"license": "MIT"
1243
},
1244
"node_modules/@types/json-schema": {
1245
"version": "7.0.15",
1246
-
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
1247
-
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
1248
"dev": true,
1249
"license": "MIT"
1250
},
1251
"node_modules/@types/node": {
1252
-
"version": "24.7.0",
1253
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
1254
-
"integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
1255
"dev": true,
1256
"license": "MIT",
1257
"dependencies": {
1258
-
"undici-types": "~7.14.0"
1259
}
1260
},
1261
"node_modules/@types/react": {
1262
-
"version": "19.2.2",
1263
-
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
1264
-
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
1265
"dev": true,
1266
"license": "MIT",
1267
"dependencies": {
1268
-
"csstype": "^3.0.2"
1269
}
1270
},
1271
"node_modules/@types/react-dom": {
1272
-
"version": "19.2.1",
1273
-
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz",
1274
-
"integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==",
1275
"dev": true,
1276
"license": "MIT",
1277
"peerDependencies": {
···
1279
}
1280
},
1281
"node_modules/@typescript-eslint/eslint-plugin": {
1282
-
"version": "8.46.0",
1283
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
1284
-
"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
1285
"dev": true,
1286
"license": "MIT",
1287
"dependencies": {
1288
"@eslint-community/regexpp": "^4.10.0",
1289
-
"@typescript-eslint/scope-manager": "8.46.0",
1290
-
"@typescript-eslint/type-utils": "8.46.0",
1291
-
"@typescript-eslint/utils": "8.46.0",
1292
-
"@typescript-eslint/visitor-keys": "8.46.0",
1293
"graphemer": "^1.4.0",
1294
"ignore": "^7.0.0",
1295
"natural-compare": "^1.4.0",
···
1303
"url": "https://opencollective.com/typescript-eslint"
1304
},
1305
"peerDependencies": {
1306
-
"@typescript-eslint/parser": "^8.46.0",
1307
"eslint": "^8.57.0 || ^9.0.0",
1308
"typescript": ">=4.8.4 <6.0.0"
1309
}
1310
},
1311
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
1312
"version": "7.0.5",
1313
-
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
1314
-
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
1315
"dev": true,
1316
"license": "MIT",
1317
"engines": {
···
1319
}
1320
},
1321
"node_modules/@typescript-eslint/parser": {
1322
-
"version": "8.46.0",
1323
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
1324
-
"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
1325
"dev": true,
1326
"license": "MIT",
1327
"dependencies": {
1328
-
"@typescript-eslint/scope-manager": "8.46.0",
1329
-
"@typescript-eslint/types": "8.46.0",
1330
-
"@typescript-eslint/typescript-estree": "8.46.0",
1331
-
"@typescript-eslint/visitor-keys": "8.46.0",
1332
"debug": "^4.3.4"
1333
},
1334
"engines": {
···
1344
}
1345
},
1346
"node_modules/@typescript-eslint/project-service": {
1347
-
"version": "8.46.0",
1348
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
1349
-
"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
1350
"dev": true,
1351
"license": "MIT",
1352
"dependencies": {
1353
-
"@typescript-eslint/tsconfig-utils": "^8.46.0",
1354
-
"@typescript-eslint/types": "^8.46.0",
1355
"debug": "^4.3.4"
1356
},
1357
"engines": {
···
1366
}
1367
},
1368
"node_modules/@typescript-eslint/scope-manager": {
1369
-
"version": "8.46.0",
1370
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
1371
-
"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
1372
"dev": true,
1373
"license": "MIT",
1374
"dependencies": {
1375
-
"@typescript-eslint/types": "8.46.0",
1376
-
"@typescript-eslint/visitor-keys": "8.46.0"
1377
},
1378
"engines": {
1379
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1384
}
1385
},
1386
"node_modules/@typescript-eslint/tsconfig-utils": {
1387
-
"version": "8.46.0",
1388
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
1389
-
"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
1390
"dev": true,
1391
"license": "MIT",
1392
"engines": {
···
1401
}
1402
},
1403
"node_modules/@typescript-eslint/type-utils": {
1404
-
"version": "8.46.0",
1405
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
1406
-
"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
1407
"dev": true,
1408
"license": "MIT",
1409
"dependencies": {
1410
-
"@typescript-eslint/types": "8.46.0",
1411
-
"@typescript-eslint/typescript-estree": "8.46.0",
1412
-
"@typescript-eslint/utils": "8.46.0",
1413
"debug": "^4.3.4",
1414
"ts-api-utils": "^2.1.0"
1415
},
···
1426
}
1427
},
1428
"node_modules/@typescript-eslint/types": {
1429
-
"version": "8.46.0",
1430
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
1431
-
"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
1432
"dev": true,
1433
"license": "MIT",
1434
"engines": {
···
1440
}
1441
},
1442
"node_modules/@typescript-eslint/typescript-estree": {
1443
-
"version": "8.46.0",
1444
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
1445
-
"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
1446
"dev": true,
1447
"license": "MIT",
1448
"dependencies": {
1449
-
"@typescript-eslint/project-service": "8.46.0",
1450
-
"@typescript-eslint/tsconfig-utils": "8.46.0",
1451
-
"@typescript-eslint/types": "8.46.0",
1452
-
"@typescript-eslint/visitor-keys": "8.46.0",
1453
"debug": "^4.3.4",
1454
-
"fast-glob": "^3.3.2",
1455
-
"is-glob": "^4.0.3",
1456
"minimatch": "^9.0.4",
1457
"semver": "^7.6.0",
1458
"ts-api-utils": "^2.1.0"
1459
},
1460
"engines": {
···
1468
"typescript": ">=4.8.4 <6.0.0"
1469
}
1470
},
1471
-
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
1472
-
"version": "2.0.2",
1473
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
1474
-
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
1475
-
"dev": true,
1476
-
"license": "MIT",
1477
-
"dependencies": {
1478
-
"balanced-match": "^1.0.0"
1479
-
}
1480
-
},
1481
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
1482
"version": "9.0.5",
1483
-
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
1484
-
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
1485
"dev": true,
1486
"license": "ISC",
1487
"dependencies": {
···
1494
"url": "https://github.com/sponsors/isaacs"
1495
}
1496
},
1497
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
1498
"version": "7.7.3",
1499
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
1500
-
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
1501
"dev": true,
1502
"license": "ISC",
1503
"bin": {
···
1508
}
1509
},
1510
"node_modules/@typescript-eslint/utils": {
1511
-
"version": "8.46.0",
1512
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
1513
-
"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
1514
"dev": true,
1515
"license": "MIT",
1516
"dependencies": {
1517
"@eslint-community/eslint-utils": "^4.7.0",
1518
-
"@typescript-eslint/scope-manager": "8.46.0",
1519
-
"@typescript-eslint/types": "8.46.0",
1520
-
"@typescript-eslint/typescript-estree": "8.46.0"
1521
},
1522
"engines": {
1523
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1532
}
1533
},
1534
"node_modules/@typescript-eslint/visitor-keys": {
1535
-
"version": "8.46.0",
1536
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
1537
-
"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
1538
"dev": true,
1539
"license": "MIT",
1540
"dependencies": {
1541
-
"@typescript-eslint/types": "8.46.0",
1542
"eslint-visitor-keys": "^4.2.1"
1543
},
1544
"engines": {
···
1550
}
1551
},
1552
"node_modules/@vitejs/plugin-react": {
1553
-
"version": "5.0.4",
1554
-
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz",
1555
-
"integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==",
1556
"dev": true,
1557
"license": "MIT",
1558
"dependencies": {
1559
-
"@babel/core": "^7.28.4",
1560
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
1561
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
1562
-
"@rolldown/pluginutils": "1.0.0-beta.38",
1563
"@types/babel__core": "^7.20.5",
1564
-
"react-refresh": "^0.17.0"
1565
},
1566
"engines": {
1567
"node": "^20.19.0 || >=22.12.0"
···
1571
}
1572
},
1573
"node_modules/@volar/language-core": {
1574
-
"version": "2.4.23",
1575
-
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz",
1576
-
"integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==",
1577
"dev": true,
1578
"license": "MIT",
1579
"dependencies": {
1580
-
"@volar/source-map": "2.4.23"
1581
}
1582
},
1583
"node_modules/@volar/source-map": {
1584
-
"version": "2.4.23",
1585
-
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz",
1586
-
"integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==",
1587
"dev": true,
1588
"license": "MIT"
1589
},
1590
"node_modules/@volar/typescript": {
1591
-
"version": "2.4.23",
1592
-
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz",
1593
-
"integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==",
1594
"dev": true,
1595
"license": "MIT",
1596
"dependencies": {
1597
-
"@volar/language-core": "2.4.23",
1598
"path-browserify": "^1.0.1",
1599
"vscode-uri": "^3.0.8"
1600
}
1601
},
1602
"node_modules/acorn": {
1603
"version": "8.15.0",
1604
-
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
1605
-
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
1606
"dev": true,
1607
"license": "MIT",
1608
"bin": {
1609
"acorn": "bin/acorn"
1610
},
···
1614
},
1615
"node_modules/acorn-jsx": {
1616
"version": "5.3.2",
1617
-
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
1618
-
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
1619
"dev": true,
1620
"license": "MIT",
1621
"peerDependencies": {
···
1623
}
1624
},
1625
"node_modules/ajv": {
1626
-
"version": "6.12.6",
1627
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
1628
-
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
1629
"dev": true,
1630
"license": "MIT",
1631
"dependencies": {
1632
-
"fast-deep-equal": "^3.1.1",
1633
-
"fast-json-stable-stringify": "^2.0.0",
1634
-
"json-schema-traverse": "^0.4.1",
1635
-
"uri-js": "^4.2.2"
1636
},
1637
"funding": {
1638
"type": "github",
1639
"url": "https://github.com/sponsors/epoberezkin"
1640
}
1641
},
1642
"node_modules/ajv-formats": {
1643
"version": "3.0.1",
1644
-
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
1645
-
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
1646
"dev": true,
1647
"license": "MIT",
1648
"dependencies": {
···
1657
}
1658
}
1659
},
1660
-
"node_modules/ajv-formats/node_modules/ajv": {
1661
-
"version": "8.17.1",
1662
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
1663
-
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
1664
-
"dev": true,
1665
-
"license": "MIT",
1666
-
"dependencies": {
1667
-
"fast-deep-equal": "^3.1.3",
1668
-
"fast-uri": "^3.0.1",
1669
-
"json-schema-traverse": "^1.0.0",
1670
-
"require-from-string": "^2.0.2"
1671
-
},
1672
-
"funding": {
1673
-
"type": "github",
1674
-
"url": "https://github.com/sponsors/epoberezkin"
1675
-
}
1676
-
},
1677
-
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
1678
-
"version": "1.0.0",
1679
-
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
1680
-
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
1681
-
"dev": true,
1682
-
"license": "MIT"
1683
-
},
1684
"node_modules/ansi-styles": {
1685
"version": "4.3.0",
1686
-
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
1687
-
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
1688
"dev": true,
1689
"license": "MIT",
1690
"dependencies": {
···
1699
},
1700
"node_modules/ansis": {
1701
"version": "4.2.0",
1702
-
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
1703
-
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
1704
"dev": true,
1705
"license": "ISC",
1706
"engines": {
···
1708
}
1709
},
1710
"node_modules/argparse": {
1711
-
"version": "2.0.1",
1712
-
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
1713
-
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
1714
"dev": true,
1715
-
"license": "Python-2.0"
1716
},
1717
"node_modules/balanced-match": {
1718
"version": "1.0.2",
1719
-
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1720
-
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
1721
"dev": true,
1722
"license": "MIT"
1723
},
1724
"node_modules/baseline-browser-mapping": {
1725
-
"version": "2.8.13",
1726
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz",
1727
-
"integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==",
1728
"dev": true,
1729
"license": "Apache-2.0",
1730
"bin": {
···
1733
},
1734
"node_modules/brace-expansion": {
1735
"version": "1.1.12",
1736
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
1737
-
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
1738
"dev": true,
1739
"license": "MIT",
1740
"dependencies": {
···
1742
"concat-map": "0.0.1"
1743
}
1744
},
1745
-
"node_modules/braces": {
1746
-
"version": "3.0.3",
1747
-
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
1748
-
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1749
-
"dev": true,
1750
-
"license": "MIT",
1751
-
"dependencies": {
1752
-
"fill-range": "^7.1.1"
1753
-
},
1754
-
"engines": {
1755
-
"node": ">=8"
1756
-
}
1757
-
},
1758
"node_modules/browserslist": {
1759
-
"version": "4.26.3",
1760
-
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
1761
-
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
1762
"dev": true,
1763
"funding": [
1764
{
···
1775
}
1776
],
1777
"license": "MIT",
1778
"dependencies": {
1779
-
"baseline-browser-mapping": "^2.8.9",
1780
-
"caniuse-lite": "^1.0.30001746",
1781
-
"electron-to-chromium": "^1.5.227",
1782
-
"node-releases": "^2.0.21",
1783
-
"update-browserslist-db": "^1.1.3"
1784
},
1785
"bin": {
1786
"browserslist": "cli.js"
···
1791
},
1792
"node_modules/buffer-from": {
1793
"version": "1.1.2",
1794
-
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
1795
-
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
1796
"dev": true,
1797
"license": "MIT",
1798
-
"optional": true,
1799
-
"peer": true
1800
},
1801
"node_modules/callsites": {
1802
"version": "3.1.0",
1803
-
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
1804
-
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
1805
"dev": true,
1806
"license": "MIT",
1807
"engines": {
···
1809
}
1810
},
1811
"node_modules/caniuse-lite": {
1812
-
"version": "1.0.30001748",
1813
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
1814
-
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
1815
"dev": true,
1816
"funding": [
1817
{
···
1831
},
1832
"node_modules/chalk": {
1833
"version": "4.1.2",
1834
-
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
1835
-
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
1836
"dev": true,
1837
"license": "MIT",
1838
"dependencies": {
···
1846
"url": "https://github.com/chalk/chalk?sponsor=1"
1847
}
1848
},
1849
"node_modules/color-convert": {
1850
"version": "2.0.1",
1851
-
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1852
-
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1853
"dev": true,
1854
"license": "MIT",
1855
"dependencies": {
···
1861
},
1862
"node_modules/color-name": {
1863
"version": "1.1.4",
1864
-
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1865
-
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1866
"dev": true,
1867
"license": "MIT"
1868
},
1869
"node_modules/commander": {
1870
"version": "2.20.3",
1871
-
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
1872
-
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
1873
"dev": true,
1874
"license": "MIT",
1875
-
"optional": true,
1876
-
"peer": true
1877
},
1878
"node_modules/commondir": {
1879
"version": "1.0.1",
1880
-
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
1881
-
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
1882
"dev": true,
1883
"license": "MIT"
1884
},
1885
"node_modules/compare-versions": {
1886
"version": "6.1.1",
1887
-
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
1888
-
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==",
1889
"dev": true,
1890
"license": "MIT"
1891
},
1892
"node_modules/concat-map": {
1893
"version": "0.0.1",
1894
-
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1895
-
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
1896
"dev": true,
1897
"license": "MIT"
1898
},
1899
"node_modules/confbox": {
1900
"version": "0.2.2",
1901
-
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
1902
-
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
1903
"dev": true,
1904
"license": "MIT"
1905
},
1906
"node_modules/convert-source-map": {
1907
"version": "2.0.0",
1908
-
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1909
-
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1910
"dev": true,
1911
"license": "MIT"
1912
},
1913
"node_modules/cross-spawn": {
1914
"version": "7.0.6",
1915
-
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
1916
-
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
1917
"dev": true,
1918
"license": "MIT",
1919
"dependencies": {
···
1926
}
1927
},
1928
"node_modules/csstype": {
1929
-
"version": "3.1.3",
1930
-
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
1931
-
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
1932
"dev": true,
1933
"license": "MIT"
1934
},
1935
"node_modules/debug": {
1936
"version": "4.4.3",
1937
-
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1938
-
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1939
"dev": true,
1940
"license": "MIT",
1941
"dependencies": {
···
1952
},
1953
"node_modules/deep-is": {
1954
"version": "0.1.4",
1955
-
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
1956
-
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
1957
"dev": true,
1958
"license": "MIT"
1959
},
1960
"node_modules/detect-libc": {
1961
"version": "2.1.2",
1962
-
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
1963
-
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
1964
"dev": true,
1965
"license": "Apache-2.0",
1966
"engines": {
1967
"node": ">=8"
1968
}
1969
},
1970
"node_modules/electron-to-chromium": {
1971
-
"version": "1.5.232",
1972
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz",
1973
-
"integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==",
1974
"dev": true,
1975
"license": "ISC"
1976
},
1977
"node_modules/escalade": {
1978
"version": "3.2.0",
1979
-
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1980
-
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1981
"dev": true,
1982
"license": "MIT",
1983
"engines": {
···
1986
},
1987
"node_modules/escape-string-regexp": {
1988
"version": "4.0.0",
1989
-
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
1990
-
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
1991
"dev": true,
1992
"license": "MIT",
1993
"engines": {
···
1998
}
1999
},
2000
"node_modules/eslint": {
2001
-
"version": "9.37.0",
2002
-
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
2003
-
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
2004
"dev": true,
2005
"license": "MIT",
2006
"dependencies": {
2007
"@eslint-community/eslint-utils": "^4.8.0",
2008
"@eslint-community/regexpp": "^4.12.1",
2009
-
"@eslint/config-array": "^0.21.0",
2010
-
"@eslint/config-helpers": "^0.4.0",
2011
-
"@eslint/core": "^0.16.0",
2012
"@eslint/eslintrc": "^3.3.1",
2013
-
"@eslint/js": "9.37.0",
2014
-
"@eslint/plugin-kit": "^0.4.0",
2015
"@humanfs/node": "^0.16.6",
2016
"@humanwhocodes/module-importer": "^1.0.1",
2017
"@humanwhocodes/retry": "^0.4.2",
2018
"@types/estree": "^1.0.6",
2019
-
"@types/json-schema": "^7.0.15",
2020
"ajv": "^6.12.4",
2021
"chalk": "^4.0.0",
2022
"cross-spawn": "^7.0.6",
···
2060
},
2061
"node_modules/eslint-plugin-react-hooks": {
2062
"version": "5.2.0",
2063
-
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
2064
-
"integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
2065
"dev": true,
2066
"license": "MIT",
2067
"engines": {
···
2072
}
2073
},
2074
"node_modules/eslint-plugin-react-refresh": {
2075
-
"version": "0.4.23",
2076
-
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz",
2077
-
"integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==",
2078
"dev": true,
2079
"license": "MIT",
2080
"peerDependencies": {
···
2083
},
2084
"node_modules/eslint-scope": {
2085
"version": "8.4.0",
2086
-
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
2087
-
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
2088
"dev": true,
2089
"license": "BSD-2-Clause",
2090
"dependencies": {
···
2100
},
2101
"node_modules/eslint-visitor-keys": {
2102
"version": "4.2.1",
2103
-
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
2104
-
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
2105
"dev": true,
2106
"license": "Apache-2.0",
2107
"engines": {
···
2111
"url": "https://opencollective.com/eslint"
2112
}
2113
},
2114
"node_modules/esm-env": {
2115
"version": "1.2.2",
2116
-
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
2117
-
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
2118
"license": "MIT"
2119
},
2120
"node_modules/espree": {
2121
"version": "10.4.0",
2122
-
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
2123
-
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
2124
"dev": true,
2125
"license": "BSD-2-Clause",
2126
"dependencies": {
···
2137
},
2138
"node_modules/esquery": {
2139
"version": "1.6.0",
2140
-
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
2141
-
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
2142
"dev": true,
2143
"license": "BSD-3-Clause",
2144
"dependencies": {
···
2150
},
2151
"node_modules/esrecurse": {
2152
"version": "4.3.0",
2153
-
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
2154
-
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
2155
"dev": true,
2156
"license": "BSD-2-Clause",
2157
"dependencies": {
···
2163
},
2164
"node_modules/estraverse": {
2165
"version": "5.3.0",
2166
-
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
2167
-
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
2168
"dev": true,
2169
"license": "BSD-2-Clause",
2170
"engines": {
···
2173
},
2174
"node_modules/estree-walker": {
2175
"version": "2.0.2",
2176
-
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
2177
-
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
2178
"dev": true,
2179
"license": "MIT"
2180
},
2181
"node_modules/esutils": {
2182
"version": "2.0.3",
2183
-
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
2184
-
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
2185
"dev": true,
2186
"license": "BSD-2-Clause",
2187
"engines": {
···
2189
}
2190
},
2191
"node_modules/exsolve": {
2192
-
"version": "1.0.7",
2193
-
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
2194
-
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
2195
"dev": true,
2196
"license": "MIT"
2197
},
2198
"node_modules/fast-deep-equal": {
2199
"version": "3.1.3",
2200
-
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
2201
-
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
2202
"dev": true,
2203
"license": "MIT"
2204
},
2205
-
"node_modules/fast-glob": {
2206
-
"version": "3.3.3",
2207
-
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
2208
-
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
2209
-
"dev": true,
2210
-
"license": "MIT",
2211
-
"dependencies": {
2212
-
"@nodelib/fs.stat": "^2.0.2",
2213
-
"@nodelib/fs.walk": "^1.2.3",
2214
-
"glob-parent": "^5.1.2",
2215
-
"merge2": "^1.3.0",
2216
-
"micromatch": "^4.0.8"
2217
-
},
2218
-
"engines": {
2219
-
"node": ">=8.6.0"
2220
-
}
2221
-
},
2222
-
"node_modules/fast-glob/node_modules/glob-parent": {
2223
-
"version": "5.1.2",
2224
-
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
2225
-
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
2226
-
"dev": true,
2227
-
"license": "ISC",
2228
-
"dependencies": {
2229
-
"is-glob": "^4.0.1"
2230
-
},
2231
-
"engines": {
2232
-
"node": ">= 6"
2233
-
}
2234
-
},
2235
"node_modules/fast-json-stable-stringify": {
2236
"version": "2.1.0",
2237
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
···
2241
},
2242
"node_modules/fast-levenshtein": {
2243
"version": "2.0.6",
2244
-
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
2245
-
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
2246
"dev": true,
2247
"license": "MIT"
2248
},
2249
"node_modules/fast-uri": {
2250
"version": "3.1.0",
2251
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
2252
-
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
2253
"dev": true,
2254
"funding": [
2255
{
···
2263
],
2264
"license": "BSD-3-Clause"
2265
},
2266
-
"node_modules/fastq": {
2267
-
"version": "1.19.1",
2268
-
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
2269
-
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
2270
"dev": true,
2271
-
"license": "ISC",
2272
-
"dependencies": {
2273
-
"reusify": "^1.0.4"
2274
}
2275
},
2276
"node_modules/file-entry-cache": {
2277
"version": "8.0.0",
2278
-
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
2279
-
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
2280
"dev": true,
2281
"license": "MIT",
2282
"dependencies": {
···
2286
"node": ">=16.0.0"
2287
}
2288
},
2289
-
"node_modules/fill-range": {
2290
-
"version": "7.1.1",
2291
-
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
2292
-
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
2293
-
"dev": true,
2294
-
"license": "MIT",
2295
-
"dependencies": {
2296
-
"to-regex-range": "^5.0.1"
2297
-
},
2298
-
"engines": {
2299
-
"node": ">=8"
2300
-
}
2301
-
},
2302
"node_modules/find-cache-dir": {
2303
"version": "3.3.2",
2304
-
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
2305
-
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
2306
"dev": true,
2307
"license": "MIT",
2308
"dependencies": {
···
2319
},
2320
"node_modules/find-up": {
2321
"version": "5.0.0",
2322
-
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
2323
-
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
2324
"dev": true,
2325
"license": "MIT",
2326
"dependencies": {
···
2336
},
2337
"node_modules/flat-cache": {
2338
"version": "4.0.1",
2339
-
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
2340
-
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
2341
"dev": true,
2342
"license": "MIT",
2343
"dependencies": {
···
2350
},
2351
"node_modules/flatted": {
2352
"version": "3.3.3",
2353
-
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
2354
-
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
2355
"dev": true,
2356
"license": "ISC"
2357
},
2358
"node_modules/fs-extra": {
2359
-
"version": "11.3.2",
2360
-
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
2361
-
"integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
2362
"dev": true,
2363
"license": "MIT",
2364
"dependencies": {
···
2367
"universalify": "^2.0.0"
2368
},
2369
"engines": {
2370
-
"node": ">=14.14"
2371
}
2372
},
2373
"node_modules/fsevents": {
···
2387
},
2388
"node_modules/function-bind": {
2389
"version": "1.1.2",
2390
-
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
2391
-
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
2392
"dev": true,
2393
"license": "MIT",
2394
"funding": {
···
2397
},
2398
"node_modules/gensync": {
2399
"version": "1.0.0-beta.2",
2400
-
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
2401
-
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
2402
"dev": true,
2403
"license": "MIT",
2404
"engines": {
···
2407
},
2408
"node_modules/glob-parent": {
2409
"version": "6.0.2",
2410
-
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
2411
-
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
2412
"dev": true,
2413
"license": "ISC",
2414
"dependencies": {
···
2419
}
2420
},
2421
"node_modules/globals": {
2422
-
"version": "16.4.0",
2423
-
"resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
2424
-
"integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
2425
"dev": true,
2426
"license": "MIT",
2427
"engines": {
···
2433
},
2434
"node_modules/graceful-fs": {
2435
"version": "4.2.11",
2436
-
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
2437
-
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
2438
"dev": true,
2439
"license": "ISC"
2440
},
2441
"node_modules/graphemer": {
2442
"version": "1.4.0",
2443
-
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
2444
-
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
2445
"dev": true,
2446
"license": "MIT"
2447
},
2448
"node_modules/has-flag": {
2449
"version": "4.0.0",
2450
-
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
2451
-
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
2452
"dev": true,
2453
"license": "MIT",
2454
"engines": {
···
2457
},
2458
"node_modules/hasown": {
2459
"version": "2.0.2",
2460
-
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
2461
-
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
2462
"dev": true,
2463
"license": "MIT",
2464
"dependencies": {
···
2470
},
2471
"node_modules/ignore": {
2472
"version": "5.3.2",
2473
-
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
2474
-
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
2475
"dev": true,
2476
"license": "MIT",
2477
"engines": {
···
2480
},
2481
"node_modules/import-fresh": {
2482
"version": "3.3.1",
2483
-
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
2484
-
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
2485
"dev": true,
2486
"license": "MIT",
2487
"dependencies": {
···
2497
},
2498
"node_modules/import-lazy": {
2499
"version": "4.0.0",
2500
-
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
2501
-
"integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
2502
"dev": true,
2503
"license": "MIT",
2504
"engines": {
···
2507
},
2508
"node_modules/imurmurhash": {
2509
"version": "0.1.4",
2510
-
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
2511
-
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
2512
"dev": true,
2513
"license": "MIT",
2514
"engines": {
···
2517
},
2518
"node_modules/is-core-module": {
2519
"version": "2.16.1",
2520
-
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
2521
-
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
2522
"dev": true,
2523
"license": "MIT",
2524
"dependencies": {
···
2533
},
2534
"node_modules/is-extglob": {
2535
"version": "2.1.1",
2536
-
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
2537
-
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
2538
"dev": true,
2539
"license": "MIT",
2540
"engines": {
···
2543
},
2544
"node_modules/is-glob": {
2545
"version": "4.0.3",
2546
-
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
2547
-
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
2548
"dev": true,
2549
"license": "MIT",
2550
"dependencies": {
···
2554
"node": ">=0.10.0"
2555
}
2556
},
2557
-
"node_modules/is-number": {
2558
-
"version": "7.0.0",
2559
-
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
2560
-
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
2561
-
"dev": true,
2562
-
"license": "MIT",
2563
-
"engines": {
2564
-
"node": ">=0.12.0"
2565
-
}
2566
-
},
2567
"node_modules/isexe": {
2568
"version": "2.0.0",
2569
-
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
2570
-
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
2571
"dev": true,
2572
"license": "ISC"
2573
},
2574
"node_modules/jju": {
2575
"version": "1.4.0",
2576
-
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
2577
-
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
2578
"dev": true,
2579
"license": "MIT"
2580
},
2581
"node_modules/js-tokens": {
2582
"version": "4.0.0",
2583
-
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
2584
-
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
2585
"dev": true,
2586
"license": "MIT"
2587
},
2588
"node_modules/js-yaml": {
2589
-
"version": "4.1.0",
2590
-
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
2591
-
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
2592
"dev": true,
2593
"license": "MIT",
2594
"dependencies": {
···
2598
"js-yaml": "bin/js-yaml.js"
2599
}
2600
},
2601
"node_modules/jsesc": {
2602
"version": "3.1.0",
2603
-
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
2604
-
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
2605
"dev": true,
2606
"license": "MIT",
2607
"bin": {
···
2613
},
2614
"node_modules/json-buffer": {
2615
"version": "3.0.1",
2616
-
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
2617
-
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
2618
"dev": true,
2619
"license": "MIT"
2620
},
2621
"node_modules/json-schema-traverse": {
2622
-
"version": "0.4.1",
2623
-
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
2624
-
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
2625
"dev": true,
2626
"license": "MIT"
2627
},
2628
"node_modules/json-stable-stringify-without-jsonify": {
2629
"version": "1.0.1",
2630
-
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
2631
-
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
2632
"dev": true,
2633
"license": "MIT"
2634
},
2635
"node_modules/json5": {
2636
"version": "2.2.3",
2637
-
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
2638
-
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
2639
"dev": true,
2640
"license": "MIT",
2641
"bin": {
···
2647
},
2648
"node_modules/jsonfile": {
2649
"version": "6.2.0",
2650
-
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
2651
-
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
2652
"dev": true,
2653
"license": "MIT",
2654
"dependencies": {
···
2660
},
2661
"node_modules/keyv": {
2662
"version": "4.5.4",
2663
-
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
2664
-
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
2665
"dev": true,
2666
"license": "MIT",
2667
"dependencies": {
···
2670
},
2671
"node_modules/kolorist": {
2672
"version": "1.8.0",
2673
-
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
2674
-
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
2675
"dev": true,
2676
"license": "MIT"
2677
},
2678
"node_modules/levn": {
2679
"version": "0.4.1",
2680
-
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
2681
-
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
2682
"dev": true,
2683
"license": "MIT",
2684
"dependencies": {
···
2691
},
2692
"node_modules/lightningcss": {
2693
"version": "1.30.2",
2694
-
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
2695
-
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
2696
"dev": true,
2697
"license": "MPL-2.0",
2698
"dependencies": {
···
2719
"lightningcss-win32-x64-msvc": "1.30.2"
2720
}
2721
},
2722
"node_modules/lightningcss-darwin-arm64": {
2723
"version": "1.30.2",
2724
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
···
2740
"url": "https://opencollective.com/parcel"
2741
}
2742
},
2743
"node_modules/local-pkg": {
2744
"version": "1.1.2",
2745
-
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
2746
-
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
2747
"dev": true,
2748
"license": "MIT",
2749
"dependencies": {
···
2760
},
2761
"node_modules/locate-path": {
2762
"version": "6.0.0",
2763
-
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
2764
-
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
2765
"dev": true,
2766
"license": "MIT",
2767
"dependencies": {
···
2776
},
2777
"node_modules/lodash": {
2778
"version": "4.17.21",
2779
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
2780
-
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
2781
"dev": true,
2782
"license": "MIT"
2783
},
2784
"node_modules/lodash.merge": {
2785
"version": "4.6.2",
2786
-
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
2787
-
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
2788
"dev": true,
2789
"license": "MIT"
2790
},
2791
"node_modules/lru-cache": {
2792
-
"version": "5.1.1",
2793
-
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
2794
-
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
2795
"dev": true,
2796
"license": "ISC",
2797
"dependencies": {
2798
-
"yallist": "^3.0.2"
2799
}
2800
},
2801
"node_modules/magic-string": {
2802
-
"version": "0.30.19",
2803
-
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
2804
-
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
2805
"dev": true,
2806
"license": "MIT",
2807
"dependencies": {
···
2810
},
2811
"node_modules/make-dir": {
2812
"version": "3.1.0",
2813
-
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
2814
-
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
2815
"dev": true,
2816
"license": "MIT",
2817
"dependencies": {
···
2824
"url": "https://github.com/sponsors/sindresorhus"
2825
}
2826
},
2827
-
"node_modules/merge2": {
2828
-
"version": "1.4.1",
2829
-
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
2830
-
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
2831
"dev": true,
2832
-
"license": "MIT",
2833
-
"engines": {
2834
-
"node": ">= 8"
2835
-
}
2836
-
},
2837
-
"node_modules/micromatch": {
2838
-
"version": "4.0.8",
2839
-
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
2840
-
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
2841
-
"dev": true,
2842
-
"license": "MIT",
2843
-
"dependencies": {
2844
-
"braces": "^3.0.3",
2845
-
"picomatch": "^2.3.1"
2846
-
},
2847
-
"engines": {
2848
-
"node": ">=8.6"
2849
}
2850
},
2851
"node_modules/minimatch": {
2852
-
"version": "3.1.2",
2853
-
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2854
-
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2855
"dev": true,
2856
"license": "ISC",
2857
"dependencies": {
2858
-
"brace-expansion": "^1.1.7"
2859
},
2860
"engines": {
2861
-
"node": "*"
2862
}
2863
},
2864
"node_modules/mlly": {
2865
"version": "1.8.0",
2866
-
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
2867
-
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
2868
"dev": true,
2869
"license": "MIT",
2870
"dependencies": {
···
2874
"ufo": "^1.6.1"
2875
}
2876
},
2877
-
"node_modules/mlly/node_modules/confbox": {
2878
-
"version": "0.1.8",
2879
-
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
2880
-
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
2881
-
"dev": true,
2882
-
"license": "MIT"
2883
-
},
2884
"node_modules/mlly/node_modules/pkg-types": {
2885
"version": "1.3.1",
2886
-
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
2887
-
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
2888
"dev": true,
2889
"license": "MIT",
2890
"dependencies": {
···
2892
"mlly": "^1.7.4",
2893
"pathe": "^2.0.1"
2894
}
2895
},
2896
"node_modules/ms": {
2897
"version": "2.1.3",
2898
-
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2899
-
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2900
"dev": true,
2901
"license": "MIT"
2902
},
2903
"node_modules/nanoid": {
2904
"version": "3.3.11",
2905
-
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
2906
-
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
2907
"dev": true,
2908
"funding": [
2909
{
···
2921
},
2922
"node_modules/natural-compare": {
2923
"version": "1.4.0",
2924
-
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
2925
-
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
2926
"dev": true,
2927
"license": "MIT"
2928
},
2929
"node_modules/node-releases": {
2930
-
"version": "2.0.23",
2931
-
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
2932
-
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
2933
"dev": true,
2934
"license": "MIT"
2935
},
2936
"node_modules/optionator": {
2937
"version": "0.9.4",
2938
-
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
2939
-
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
2940
"dev": true,
2941
"license": "MIT",
2942
"dependencies": {
···
2953
},
2954
"node_modules/p-limit": {
2955
"version": "3.1.0",
2956
-
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
2957
-
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
2958
"dev": true,
2959
"license": "MIT",
2960
"dependencies": {
···
2969
},
2970
"node_modules/p-locate": {
2971
"version": "5.0.0",
2972
-
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
2973
-
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
2974
"dev": true,
2975
"license": "MIT",
2976
"dependencies": {
···
2985
},
2986
"node_modules/p-try": {
2987
"version": "2.2.0",
2988
-
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
2989
-
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
2990
"dev": true,
2991
"license": "MIT",
2992
"engines": {
···
2995
},
2996
"node_modules/parent-module": {
2997
"version": "1.0.1",
2998
-
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
2999
-
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
3000
"dev": true,
3001
"license": "MIT",
3002
"dependencies": {
···
3008
},
3009
"node_modules/path-browserify": {
3010
"version": "1.0.1",
3011
-
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
3012
-
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
3013
"dev": true,
3014
"license": "MIT"
3015
},
3016
"node_modules/path-exists": {
3017
"version": "4.0.0",
3018
-
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
3019
-
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
3020
"dev": true,
3021
"license": "MIT",
3022
"engines": {
···
3025
},
3026
"node_modules/path-key": {
3027
"version": "3.1.1",
3028
-
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
3029
-
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
3030
"dev": true,
3031
"license": "MIT",
3032
"engines": {
···
3035
},
3036
"node_modules/path-parse": {
3037
"version": "1.0.7",
3038
-
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
3039
-
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
3040
"dev": true,
3041
"license": "MIT"
3042
},
3043
"node_modules/pathe": {
3044
"version": "2.0.3",
3045
-
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
3046
-
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
3047
"dev": true,
3048
"license": "MIT"
3049
},
3050
"node_modules/picocolors": {
3051
"version": "1.1.1",
3052
-
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
3053
-
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
3054
"dev": true,
3055
"license": "ISC"
3056
},
3057
"node_modules/picomatch": {
3058
-
"version": "2.3.1",
3059
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
3060
-
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
3061
"dev": true,
3062
"license": "MIT",
3063
"engines": {
3064
-
"node": ">=8.6"
3065
},
3066
"funding": {
3067
"url": "https://github.com/sponsors/jonschlinkert"
···
3069
},
3070
"node_modules/pkg-dir": {
3071
"version": "4.2.0",
3072
-
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
3073
-
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
3074
"dev": true,
3075
"license": "MIT",
3076
"dependencies": {
···
3082
},
3083
"node_modules/pkg-dir/node_modules/find-up": {
3084
"version": "4.1.0",
3085
-
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
3086
-
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
3087
"dev": true,
3088
"license": "MIT",
3089
"dependencies": {
···
3094
"node": ">=8"
3095
}
3096
},
3097
-
"node_modules/pkg-dir/node_modules/locate-path": {
3098
"version": "5.0.0",
3099
-
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
3100
-
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
3101
"dev": true,
3102
"license": "MIT",
3103
"dependencies": {
···
3107
"node": ">=8"
3108
}
3109
},
3110
-
"node_modules/pkg-dir/node_modules/p-limit": {
3111
-
"version": "2.3.0",
3112
-
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
3113
-
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
3114
"dev": true,
3115
"license": "MIT",
3116
"dependencies": {
3117
-
"p-try": "^2.0.0"
3118
},
3119
"engines": {
3120
-
"node": ">=6"
3121
-
},
3122
-
"funding": {
3123
-
"url": "https://github.com/sponsors/sindresorhus"
3124
}
3125
},
3126
-
"node_modules/pkg-dir/node_modules/p-locate": {
3127
-
"version": "4.1.0",
3128
-
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
3129
-
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
3130
"dev": true,
3131
"license": "MIT",
3132
"dependencies": {
3133
-
"p-limit": "^2.2.0"
3134
},
3135
"engines": {
3136
-
"node": ">=8"
3137
}
3138
},
3139
"node_modules/pkg-types": {
3140
"version": "2.3.0",
3141
-
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
3142
-
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
3143
"dev": true,
3144
"license": "MIT",
3145
"dependencies": {
···
3150
},
3151
"node_modules/postcss": {
3152
"version": "8.5.6",
3153
-
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
3154
-
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
3155
"dev": true,
3156
"funding": [
3157
{
···
3179
},
3180
"node_modules/prelude-ls": {
3181
"version": "1.2.1",
3182
-
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
3183
-
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
3184
"dev": true,
3185
"license": "MIT",
3186
"engines": {
···
3189
},
3190
"node_modules/punycode": {
3191
"version": "2.3.1",
3192
-
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
3193
-
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
3194
"dev": true,
3195
"license": "MIT",
3196
"engines": {
···
3199
},
3200
"node_modules/quansync": {
3201
"version": "0.2.11",
3202
-
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
3203
-
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
3204
"dev": true,
3205
"funding": [
3206
{
···
3214
],
3215
"license": "MIT"
3216
},
3217
-
"node_modules/queue-microtask": {
3218
-
"version": "1.2.3",
3219
-
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
3220
-
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
3221
-
"dev": true,
3222
-
"funding": [
3223
-
{
3224
-
"type": "github",
3225
-
"url": "https://github.com/sponsors/feross"
3226
-
},
3227
-
{
3228
-
"type": "patreon",
3229
-
"url": "https://www.patreon.com/feross"
3230
-
},
3231
-
{
3232
-
"type": "consulting",
3233
-
"url": "https://feross.org/support"
3234
-
}
3235
-
],
3236
-
"license": "MIT"
3237
-
},
3238
"node_modules/react": {
3239
"version": "19.2.0",
3240
-
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
3241
-
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
3242
"dev": true,
3243
"license": "MIT",
3244
"engines": {
3245
"node": ">=0.10.0"
3246
}
3247
},
3248
"node_modules/react-dom": {
3249
"version": "19.2.0",
3250
-
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
3251
-
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
3252
"dev": true,
3253
"license": "MIT",
3254
"dependencies": {
···
3259
}
3260
},
3261
"node_modules/react-refresh": {
3262
-
"version": "0.17.0",
3263
-
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
3264
-
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
3265
"dev": true,
3266
"license": "MIT",
3267
"engines": {
···
3270
},
3271
"node_modules/require-from-string": {
3272
"version": "2.0.2",
3273
-
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
3274
-
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
3275
"dev": true,
3276
"license": "MIT",
3277
"engines": {
···
3279
}
3280
},
3281
"node_modules/resolve": {
3282
-
"version": "1.22.10",
3283
-
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
3284
-
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
3285
"dev": true,
3286
"license": "MIT",
3287
"dependencies": {
3288
-
"is-core-module": "^2.16.0",
3289
"path-parse": "^1.0.7",
3290
"supports-preserve-symlinks-flag": "^1.0.0"
3291
},
···
3301
},
3302
"node_modules/resolve-from": {
3303
"version": "4.0.0",
3304
-
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
3305
-
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
3306
"dev": true,
3307
"license": "MIT",
3308
"engines": {
3309
"node": ">=4"
3310
}
3311
},
3312
-
"node_modules/reusify": {
3313
-
"version": "1.1.0",
3314
-
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
3315
-
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
3316
-
"dev": true,
3317
-
"license": "MIT",
3318
-
"engines": {
3319
-
"iojs": ">=1.0.0",
3320
-
"node": ">=0.10.0"
3321
-
}
3322
-
},
3323
"node_modules/rolldown": {
3324
"version": "1.0.0-beta.41",
3325
-
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.41.tgz",
3326
-
"integrity": "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==",
3327
"dev": true,
3328
"license": "MIT",
3329
"dependencies": {
3330
"@oxc-project/types": "=0.93.0",
3331
"@rolldown/pluginutils": "1.0.0-beta.41",
···
3356
},
3357
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
3358
"version": "1.0.0-beta.41",
3359
-
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.41.tgz",
3360
-
"integrity": "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==",
3361
"dev": true,
3362
"license": "MIT"
3363
},
3364
"node_modules/rollup": {
3365
-
"version": "4.52.4",
3366
-
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
3367
-
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
3368
"dev": true,
3369
"license": "MIT",
3370
"peer": true,
···
3379
"npm": ">=8.0.0"
3380
},
3381
"optionalDependencies": {
3382
-
"@rollup/rollup-android-arm-eabi": "4.52.4",
3383
-
"@rollup/rollup-android-arm64": "4.52.4",
3384
-
"@rollup/rollup-darwin-arm64": "4.52.4",
3385
-
"@rollup/rollup-darwin-x64": "4.52.4",
3386
-
"@rollup/rollup-freebsd-arm64": "4.52.4",
3387
-
"@rollup/rollup-freebsd-x64": "4.52.4",
3388
-
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
3389
-
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
3390
-
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
3391
-
"@rollup/rollup-linux-arm64-musl": "4.52.4",
3392
-
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
3393
-
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
3394
-
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
3395
-
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
3396
-
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
3397
-
"@rollup/rollup-linux-x64-gnu": "4.52.4",
3398
-
"@rollup/rollup-linux-x64-musl": "4.52.4",
3399
-
"@rollup/rollup-openharmony-arm64": "4.52.4",
3400
-
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
3401
-
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
3402
-
"@rollup/rollup-win32-x64-gnu": "4.52.4",
3403
-
"@rollup/rollup-win32-x64-msvc": "4.52.4",
3404
"fsevents": "~2.3.2"
3405
}
3406
},
3407
"node_modules/rollup-plugin-typescript2": {
3408
"version": "0.36.0",
3409
-
"resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz",
3410
-
"integrity": "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==",
3411
"dev": true,
3412
"license": "MIT",
3413
"dependencies": {
···
3422
"typescript": ">=2.4.0"
3423
}
3424
},
3425
-
"node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": {
3426
-
"version": "4.2.1",
3427
-
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
3428
-
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
3429
-
"dev": true,
3430
-
"license": "MIT",
3431
-
"dependencies": {
3432
-
"estree-walker": "^2.0.1",
3433
-
"picomatch": "^2.2.2"
3434
-
},
3435
-
"engines": {
3436
-
"node": ">= 8.0.0"
3437
-
}
3438
-
},
3439
-
"node_modules/rollup-plugin-typescript2/node_modules/fs-extra": {
3440
-
"version": "10.1.0",
3441
-
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
3442
-
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
3443
-
"dev": true,
3444
-
"license": "MIT",
3445
-
"dependencies": {
3446
-
"graceful-fs": "^4.2.0",
3447
-
"jsonfile": "^6.0.1",
3448
-
"universalify": "^2.0.0"
3449
-
},
3450
-
"engines": {
3451
-
"node": ">=12"
3452
-
}
3453
-
},
3454
"node_modules/rollup-plugin-typescript2/node_modules/semver": {
3455
"version": "7.7.3",
3456
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
3457
-
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
3458
"dev": true,
3459
"license": "ISC",
3460
"bin": {
···
3464
"node": ">=10"
3465
}
3466
},
3467
-
"node_modules/run-parallel": {
3468
-
"version": "1.2.0",
3469
-
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
3470
-
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
3471
-
"dev": true,
3472
-
"funding": [
3473
-
{
3474
-
"type": "github",
3475
-
"url": "https://github.com/sponsors/feross"
3476
-
},
3477
-
{
3478
-
"type": "patreon",
3479
-
"url": "https://www.patreon.com/feross"
3480
-
},
3481
-
{
3482
-
"type": "consulting",
3483
-
"url": "https://feross.org/support"
3484
-
}
3485
-
],
3486
-
"license": "MIT",
3487
-
"dependencies": {
3488
-
"queue-microtask": "^1.2.2"
3489
-
}
3490
-
},
3491
"node_modules/scheduler": {
3492
"version": "0.27.0",
3493
-
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
3494
-
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
3495
"dev": true,
3496
"license": "MIT"
3497
},
3498
"node_modules/semver": {
3499
-
"version": "6.3.1",
3500
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
3501
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
3502
"dev": true,
3503
"license": "ISC",
3504
"bin": {
3505
"semver": "bin/semver.js"
3506
}
3507
},
3508
"node_modules/shebang-command": {
3509
"version": "2.0.0",
3510
-
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
3511
-
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
3512
"dev": true,
3513
"license": "MIT",
3514
"dependencies": {
···
3520
},
3521
"node_modules/shebang-regex": {
3522
"version": "3.0.0",
3523
-
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
3524
-
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
3525
"dev": true,
3526
"license": "MIT",
3527
"engines": {
···
3530
},
3531
"node_modules/source-map": {
3532
"version": "0.6.1",
3533
-
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
3534
-
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
3535
"dev": true,
3536
"license": "BSD-3-Clause",
3537
"engines": {
···
3540
},
3541
"node_modules/source-map-js": {
3542
"version": "1.2.1",
3543
-
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
3544
-
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
3545
"dev": true,
3546
"license": "BSD-3-Clause",
3547
"engines": {
···
3550
},
3551
"node_modules/source-map-support": {
3552
"version": "0.5.21",
3553
-
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
3554
-
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
3555
"dev": true,
3556
"license": "MIT",
3557
"optional": true,
3558
-
"peer": true,
3559
"dependencies": {
3560
"buffer-from": "^1.0.0",
3561
"source-map": "^0.6.0"
···
3563
},
3564
"node_modules/sprintf-js": {
3565
"version": "1.0.3",
3566
-
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
3567
-
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
3568
"dev": true,
3569
"license": "BSD-3-Clause"
3570
},
3571
"node_modules/string-argv": {
3572
"version": "0.3.2",
3573
-
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
3574
-
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
3575
"dev": true,
3576
"license": "MIT",
3577
"engines": {
···
3580
},
3581
"node_modules/strip-json-comments": {
3582
"version": "3.1.1",
3583
-
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
3584
-
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
3585
"dev": true,
3586
"license": "MIT",
3587
"engines": {
···
3592
}
3593
},
3594
"node_modules/supports-color": {
3595
-
"version": "7.2.0",
3596
-
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
3597
-
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
3598
"dev": true,
3599
"license": "MIT",
3600
"dependencies": {
3601
"has-flag": "^4.0.0"
3602
},
3603
"engines": {
3604
-
"node": ">=8"
3605
}
3606
},
3607
"node_modules/supports-preserve-symlinks-flag": {
3608
"version": "1.0.0",
3609
-
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
3610
-
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
3611
"dev": true,
3612
"license": "MIT",
3613
"engines": {
···
3617
"url": "https://github.com/sponsors/ljharb"
3618
}
3619
},
3620
-
"node_modules/terser": {
3621
-
"version": "5.44.0",
3622
-
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
3623
-
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
3624
-
"dev": true,
3625
-
"license": "BSD-2-Clause",
3626
-
"optional": true,
3627
-
"peer": true,
3628
-
"dependencies": {
3629
-
"@jridgewell/source-map": "^0.3.3",
3630
-
"acorn": "^8.15.0",
3631
-
"commander": "^2.20.0",
3632
-
"source-map-support": "~0.5.20"
3633
-
},
3634
-
"bin": {
3635
-
"terser": "bin/terser"
3636
-
},
3637
-
"engines": {
3638
-
"node": ">=10"
3639
-
}
3640
-
},
3641
"node_modules/tinyglobby": {
3642
"version": "0.2.15",
3643
-
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
3644
-
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
3645
"dev": true,
3646
"license": "MIT",
3647
"dependencies": {
···
3655
"url": "https://github.com/sponsors/SuperchupuDev"
3656
}
3657
},
3658
-
"node_modules/tinyglobby/node_modules/fdir": {
3659
-
"version": "6.5.0",
3660
-
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
3661
-
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
3662
-
"dev": true,
3663
-
"license": "MIT",
3664
-
"engines": {
3665
-
"node": ">=12.0.0"
3666
-
},
3667
-
"peerDependencies": {
3668
-
"picomatch": "^3 || ^4"
3669
-
},
3670
-
"peerDependenciesMeta": {
3671
-
"picomatch": {
3672
-
"optional": true
3673
-
}
3674
-
}
3675
-
},
3676
-
"node_modules/tinyglobby/node_modules/picomatch": {
3677
-
"version": "4.0.3",
3678
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
3679
-
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3680
-
"dev": true,
3681
-
"license": "MIT",
3682
-
"engines": {
3683
-
"node": ">=12"
3684
-
},
3685
-
"funding": {
3686
-
"url": "https://github.com/sponsors/jonschlinkert"
3687
-
}
3688
-
},
3689
-
"node_modules/to-regex-range": {
3690
-
"version": "5.0.1",
3691
-
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
3692
-
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
3693
-
"dev": true,
3694
-
"license": "MIT",
3695
-
"dependencies": {
3696
-
"is-number": "^7.0.0"
3697
-
},
3698
-
"engines": {
3699
-
"node": ">=8.0"
3700
-
}
3701
-
},
3702
"node_modules/ts-api-utils": {
3703
"version": "2.1.0",
3704
-
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
3705
-
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
3706
"dev": true,
3707
"license": "MIT",
3708
"engines": {
···
3714
},
3715
"node_modules/tslib": {
3716
"version": "2.8.1",
3717
-
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
3718
-
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
3719
"dev": true,
3720
"license": "0BSD"
3721
},
3722
"node_modules/type-check": {
3723
"version": "0.4.0",
3724
-
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
3725
-
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
3726
"dev": true,
3727
"license": "MIT",
3728
"dependencies": {
···
3738
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
3739
"dev": true,
3740
"license": "Apache-2.0",
3741
"bin": {
3742
"tsc": "bin/tsc",
3743
"tsserver": "bin/tsserver"
···
3747
}
3748
},
3749
"node_modules/typescript-eslint": {
3750
-
"version": "8.46.0",
3751
-
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.0.tgz",
3752
-
"integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==",
3753
"dev": true,
3754
"license": "MIT",
3755
"dependencies": {
3756
-
"@typescript-eslint/eslint-plugin": "8.46.0",
3757
-
"@typescript-eslint/parser": "8.46.0",
3758
-
"@typescript-eslint/typescript-estree": "8.46.0",
3759
-
"@typescript-eslint/utils": "8.46.0"
3760
},
3761
"engines": {
3762
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
3772
},
3773
"node_modules/ufo": {
3774
"version": "1.6.1",
3775
-
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
3776
-
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
3777
"dev": true,
3778
"license": "MIT"
3779
},
3780
"node_modules/undici-types": {
3781
-
"version": "7.14.0",
3782
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
3783
-
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
3784
"dev": true,
3785
"license": "MIT"
3786
},
3787
"node_modules/universalify": {
3788
"version": "2.0.1",
3789
-
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
3790
-
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
3791
"dev": true,
3792
"license": "MIT",
3793
"engines": {
···
3795
}
3796
},
3797
"node_modules/unplugin": {
3798
-
"version": "2.3.10",
3799
-
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
3800
-
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
3801
"dev": true,
3802
"license": "MIT",
3803
"dependencies": {
···
3812
},
3813
"node_modules/unplugin-dts": {
3814
"version": "1.0.0-beta.6",
3815
-
"resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.0.0-beta.6.tgz",
3816
-
"integrity": "sha512-+xbFv5aVFtLZFNBAKI4+kXmd2h+T42/AaP8Bsp0YP/je/uOTN94Ame2Xt3e9isZS+Z7/hrLCLbsVJh+saqFMfQ==",
3817
"dev": true,
3818
"license": "MIT",
3819
"dependencies": {
···
3864
}
3865
}
3866
},
3867
-
"node_modules/unplugin/node_modules/picomatch": {
3868
-
"version": "4.0.3",
3869
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
3870
-
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3871
"dev": true,
3872
"license": "MIT",
3873
"engines": {
3874
-
"node": ">=12"
3875
},
3876
-
"funding": {
3877
-
"url": "https://github.com/sponsors/jonschlinkert"
3878
}
3879
},
3880
"node_modules/update-browserslist-db": {
3881
-
"version": "1.1.3",
3882
-
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
3883
-
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
3884
"dev": true,
3885
"funding": [
3886
{
···
3910
},
3911
"node_modules/uri-js": {
3912
"version": "4.4.1",
3913
-
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
3914
-
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
3915
"dev": true,
3916
"license": "BSD-2-Clause",
3917
"dependencies": {
···
3921
"node_modules/vite": {
3922
"name": "rolldown-vite",
3923
"version": "7.1.14",
3924
-
"resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz",
3925
-
"integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==",
3926
"dev": true,
3927
"license": "MIT",
3928
"dependencies": {
3929
"@oxc-project/runtime": "0.92.0",
3930
"fdir": "^6.5.0",
···
3995
}
3996
}
3997
},
3998
-
"node_modules/vite/node_modules/fdir": {
3999
-
"version": "6.5.0",
4000
-
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
4001
-
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
4002
-
"dev": true,
4003
-
"license": "MIT",
4004
-
"engines": {
4005
-
"node": ">=12.0.0"
4006
-
},
4007
-
"peerDependencies": {
4008
-
"picomatch": "^3 || ^4"
4009
-
},
4010
-
"peerDependenciesMeta": {
4011
-
"picomatch": {
4012
-
"optional": true
4013
-
}
4014
-
}
4015
-
},
4016
-
"node_modules/vite/node_modules/picomatch": {
4017
-
"version": "4.0.3",
4018
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
4019
-
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
4020
-
"dev": true,
4021
-
"license": "MIT",
4022
-
"engines": {
4023
-
"node": ">=12"
4024
-
},
4025
-
"funding": {
4026
-
"url": "https://github.com/sponsors/jonschlinkert"
4027
-
}
4028
-
},
4029
"node_modules/vscode-uri": {
4030
"version": "3.1.0",
4031
-
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
4032
-
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
4033
"dev": true,
4034
"license": "MIT"
4035
},
4036
"node_modules/webpack-virtual-modules": {
4037
"version": "0.6.2",
4038
-
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
4039
-
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
4040
"dev": true,
4041
"license": "MIT"
4042
},
4043
"node_modules/which": {
4044
"version": "2.0.2",
4045
-
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
4046
-
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
4047
"dev": true,
4048
"license": "ISC",
4049
"dependencies": {
···
4058
},
4059
"node_modules/word-wrap": {
4060
"version": "1.2.5",
4061
-
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
4062
-
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
4063
"dev": true,
4064
"license": "MIT",
4065
"engines": {
···
4067
}
4068
},
4069
"node_modules/yallist": {
4070
-
"version": "3.1.1",
4071
-
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
4072
-
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
4073
"dev": true,
4074
"license": "ISC"
4075
},
4076
"node_modules/yocto-queue": {
4077
"version": "0.1.0",
4078
-
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
4079
-
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
4080
"dev": true,
4081
"license": "MIT",
4082
"engines": {
···
1
{
2
"name": "atproto-ui",
3
+
"version": "0.12",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "atproto-ui",
9
+
"version": "0.12",
10
"dependencies": {
11
"@atcute/atproto": "^3.1.7",
12
"@atcute/bluesky": "^3.2.3",
13
"@atcute/client": "^4.0.3",
14
"@atcute/identity-resolver": "^1.1.3",
15
+
"@atcute/tangled": "^1.0.10"
16
},
17
"devDependencies": {
18
"@eslint/js": "^9.36.0",
···
44
}
45
},
46
"node_modules/@atcute/atproto": {
47
+
"version": "3.1.9",
48
"license": "0BSD",
49
"dependencies": {
50
"@atcute/lexicons": "^1.2.2"
51
}
52
},
53
"node_modules/@atcute/bluesky": {
54
+
"version": "3.2.11",
55
"license": "0BSD",
56
"dependencies": {
57
+
"@atcute/atproto": "^3.1.9",
58
+
"@atcute/lexicons": "^1.2.5"
59
}
60
},
61
"node_modules/@atcute/client": {
62
+
"version": "4.1.0",
63
+
"license": "0BSD",
64
"dependencies": {
65
+
"@atcute/identity": "^1.1.3",
66
+
"@atcute/lexicons": "^1.2.5"
67
}
68
},
69
"node_modules/@atcute/identity": {
70
+
"version": "1.1.3",
71
"license": "0BSD",
72
+
"peer": true,
73
"dependencies": {
74
+
"@atcute/lexicons": "^1.2.4",
75
+
"@badrap/valita": "^0.4.6"
76
}
77
},
78
"node_modules/@atcute/identity-resolver": {
79
"version": "1.1.4",
80
"license": "0BSD",
81
"dependencies": {
82
"@atcute/lexicons": "^1.2.2",
···
88
}
89
},
90
"node_modules/@atcute/lexicons": {
91
+
"version": "1.2.5",
92
"license": "0BSD",
93
"dependencies": {
94
"@standard-schema/spec": "^1.0.0",
···
96
}
97
},
98
"node_modules/@atcute/tangled": {
99
+
"version": "1.0.12",
100
"license": "0BSD",
101
"dependencies": {
102
+
"@atcute/atproto": "^3.1.9",
103
+
"@atcute/lexicons": "^1.2.3"
104
}
105
},
106
"node_modules/@atcute/util-fetch": {
107
+
"version": "1.0.4",
108
"license": "0BSD",
109
"dependencies": {
110
"@badrap/valita": "^0.4.6"
···
112
},
113
"node_modules/@babel/code-frame": {
114
"version": "7.27.1",
115
"dev": true,
116
"license": "MIT",
117
"dependencies": {
···
124
}
125
},
126
"node_modules/@babel/compat-data": {
127
+
"version": "7.28.5",
128
"dev": true,
129
"license": "MIT",
130
"engines": {
···
132
}
133
},
134
"node_modules/@babel/core": {
135
+
"version": "7.28.5",
136
"dev": true,
137
"license": "MIT",
138
+
"peer": true,
139
"dependencies": {
140
"@babel/code-frame": "^7.27.1",
141
+
"@babel/generator": "^7.28.5",
142
"@babel/helper-compilation-targets": "^7.27.2",
143
"@babel/helper-module-transforms": "^7.28.3",
144
"@babel/helpers": "^7.28.4",
145
+
"@babel/parser": "^7.28.5",
146
"@babel/template": "^7.27.2",
147
+
"@babel/traverse": "^7.28.5",
148
+
"@babel/types": "^7.28.5",
149
"@jridgewell/remapping": "^2.3.5",
150
"convert-source-map": "^2.0.0",
151
"debug": "^4.1.0",
···
161
"url": "https://opencollective.com/babel"
162
}
163
},
164
+
"node_modules/@babel/core/node_modules/semver": {
165
+
"version": "6.3.1",
166
+
"dev": true,
167
+
"license": "ISC",
168
+
"bin": {
169
+
"semver": "bin/semver.js"
170
+
}
171
+
},
172
"node_modules/@babel/generator": {
173
+
"version": "7.28.5",
174
"dev": true,
175
"license": "MIT",
176
"dependencies": {
177
+
"@babel/parser": "^7.28.5",
178
+
"@babel/types": "^7.28.5",
179
"@jridgewell/gen-mapping": "^0.3.12",
180
"@jridgewell/trace-mapping": "^0.3.28",
181
"jsesc": "^3.0.2"
···
186
},
187
"node_modules/@babel/helper-compilation-targets": {
188
"version": "7.27.2",
189
"dev": true,
190
"license": "MIT",
191
"dependencies": {
···
199
"node": ">=6.9.0"
200
}
201
},
202
+
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
203
+
"version": "5.1.1",
204
+
"dev": true,
205
+
"license": "ISC",
206
+
"dependencies": {
207
+
"yallist": "^3.0.2"
208
+
}
209
+
},
210
+
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/node_modules/yallist": {
211
+
"version": "3.1.1",
212
+
"dev": true,
213
+
"license": "ISC"
214
+
},
215
+
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
216
+
"version": "6.3.1",
217
+
"dev": true,
218
+
"license": "ISC",
219
+
"bin": {
220
+
"semver": "bin/semver.js"
221
+
}
222
+
},
223
"node_modules/@babel/helper-globals": {
224
"version": "7.28.0",
225
"dev": true,
226
"license": "MIT",
227
"engines": {
···
230
},
231
"node_modules/@babel/helper-module-imports": {
232
"version": "7.27.1",
233
"dev": true,
234
"license": "MIT",
235
"dependencies": {
···
242
},
243
"node_modules/@babel/helper-module-transforms": {
244
"version": "7.28.3",
245
"dev": true,
246
"license": "MIT",
247
"dependencies": {
···
258
},
259
"node_modules/@babel/helper-plugin-utils": {
260
"version": "7.27.1",
261
"dev": true,
262
"license": "MIT",
263
"engines": {
···
266
},
267
"node_modules/@babel/helper-string-parser": {
268
"version": "7.27.1",
269
"dev": true,
270
"license": "MIT",
271
"engines": {
···
273
}
274
},
275
"node_modules/@babel/helper-validator-identifier": {
276
+
"version": "7.28.5",
277
"dev": true,
278
"license": "MIT",
279
"engines": {
···
282
},
283
"node_modules/@babel/helper-validator-option": {
284
"version": "7.27.1",
285
"dev": true,
286
"license": "MIT",
287
"engines": {
···
290
},
291
"node_modules/@babel/helpers": {
292
"version": "7.28.4",
293
"dev": true,
294
"license": "MIT",
295
"dependencies": {
···
301
}
302
},
303
"node_modules/@babel/parser": {
304
+
"version": "7.28.5",
305
"dev": true,
306
"license": "MIT",
307
"dependencies": {
308
+
"@babel/types": "^7.28.5"
309
},
310
"bin": {
311
"parser": "bin/babel-parser.js"
···
316
},
317
"node_modules/@babel/plugin-transform-react-jsx-self": {
318
"version": "7.27.1",
319
"dev": true,
320
"license": "MIT",
321
"dependencies": {
···
330
},
331
"node_modules/@babel/plugin-transform-react-jsx-source": {
332
"version": "7.27.1",
333
"dev": true,
334
"license": "MIT",
335
"dependencies": {
···
344
},
345
"node_modules/@babel/template": {
346
"version": "7.27.2",
347
"dev": true,
348
"license": "MIT",
349
"dependencies": {
···
356
}
357
},
358
"node_modules/@babel/traverse": {
359
+
"version": "7.28.5",
360
"dev": true,
361
"license": "MIT",
362
"dependencies": {
363
"@babel/code-frame": "^7.27.1",
364
+
"@babel/generator": "^7.28.5",
365
"@babel/helper-globals": "^7.28.0",
366
+
"@babel/parser": "^7.28.5",
367
"@babel/template": "^7.27.2",
368
+
"@babel/types": "^7.28.5",
369
"debug": "^4.3.1"
370
},
371
"engines": {
···
373
}
374
},
375
"node_modules/@babel/types": {
376
+
"version": "7.28.5",
377
"dev": true,
378
"license": "MIT",
379
"dependencies": {
380
"@babel/helper-string-parser": "^7.27.1",
381
+
"@babel/helper-validator-identifier": "^7.28.5"
382
},
383
"engines": {
384
"node": ">=6.9.0"
···
386
},
387
"node_modules/@badrap/valita": {
388
"version": "0.4.6",
389
"license": "MIT",
390
"engines": {
391
"node": ">= 18"
392
}
393
},
394
+
"node_modules/@emnapi/core": {
395
+
"version": "1.7.1",
396
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
397
+
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
398
+
"dev": true,
399
+
"license": "MIT",
400
+
"optional": true,
401
+
"dependencies": {
402
+
"@emnapi/wasi-threads": "1.1.0",
403
+
"tslib": "^2.4.0"
404
+
}
405
+
},
406
+
"node_modules/@emnapi/runtime": {
407
+
"version": "1.7.1",
408
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
409
+
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
410
+
"dev": true,
411
+
"license": "MIT",
412
+
"optional": true,
413
+
"dependencies": {
414
+
"tslib": "^2.4.0"
415
+
}
416
+
},
417
+
"node_modules/@emnapi/wasi-threads": {
418
+
"version": "1.1.0",
419
+
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
420
+
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
421
+
"dev": true,
422
+
"license": "MIT",
423
+
"optional": true,
424
+
"dependencies": {
425
+
"tslib": "^2.4.0"
426
+
}
427
+
},
428
"node_modules/@eslint-community/eslint-utils": {
429
"version": "4.9.0",
430
"dev": true,
431
"license": "MIT",
432
"dependencies": {
···
444
},
445
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
446
"version": "3.4.3",
447
"dev": true,
448
"license": "Apache-2.0",
449
"engines": {
···
454
}
455
},
456
"node_modules/@eslint-community/regexpp": {
457
+
"version": "4.12.2",
458
"dev": true,
459
"license": "MIT",
460
"engines": {
···
462
}
463
},
464
"node_modules/@eslint/config-array": {
465
+
"version": "0.21.1",
466
"dev": true,
467
"license": "Apache-2.0",
468
"dependencies": {
469
+
"@eslint/object-schema": "^2.1.7",
470
"debug": "^4.3.1",
471
"minimatch": "^3.1.2"
472
},
···
474
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
475
}
476
},
477
+
"node_modules/@eslint/config-array/node_modules/minimatch": {
478
+
"version": "3.1.2",
479
+
"dev": true,
480
+
"license": "ISC",
481
+
"dependencies": {
482
+
"brace-expansion": "^1.1.7"
483
+
},
484
+
"engines": {
485
+
"node": "*"
486
+
}
487
+
},
488
"node_modules/@eslint/config-helpers": {
489
+
"version": "0.4.2",
490
"dev": true,
491
"license": "Apache-2.0",
492
"dependencies": {
493
+
"@eslint/core": "^0.17.0"
494
},
495
"engines": {
496
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
497
}
498
},
499
"node_modules/@eslint/core": {
500
+
"version": "0.17.0",
501
"dev": true,
502
"license": "Apache-2.0",
503
"dependencies": {
···
508
}
509
},
510
"node_modules/@eslint/eslintrc": {
511
+
"version": "3.3.3",
512
"dev": true,
513
"license": "MIT",
514
"dependencies": {
···
518
"globals": "^14.0.0",
519
"ignore": "^5.2.0",
520
"import-fresh": "^3.2.1",
521
+
"js-yaml": "^4.1.1",
522
"minimatch": "^3.1.2",
523
"strip-json-comments": "^3.1.1"
524
},
···
529
"url": "https://opencollective.com/eslint"
530
}
531
},
532
+
"node_modules/@eslint/eslintrc/node_modules/ajv": {
533
+
"version": "6.12.6",
534
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
535
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
536
+
"dev": true,
537
+
"license": "MIT",
538
+
"dependencies": {
539
+
"fast-deep-equal": "^3.1.1",
540
+
"fast-json-stable-stringify": "^2.0.0",
541
+
"json-schema-traverse": "^0.4.1",
542
+
"uri-js": "^4.2.2"
543
+
},
544
+
"funding": {
545
+
"type": "github",
546
+
"url": "https://github.com/sponsors/epoberezkin"
547
+
}
548
+
},
549
"node_modules/@eslint/eslintrc/node_modules/globals": {
550
"version": "14.0.0",
551
"dev": true,
552
"license": "MIT",
553
"engines": {
···
557
"url": "https://github.com/sponsors/sindresorhus"
558
}
559
},
560
+
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
561
+
"version": "0.4.1",
562
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
563
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
564
+
"dev": true,
565
+
"license": "MIT"
566
+
},
567
+
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
568
+
"version": "3.1.2",
569
+
"dev": true,
570
+
"license": "ISC",
571
+
"dependencies": {
572
+
"brace-expansion": "^1.1.7"
573
+
},
574
+
"engines": {
575
+
"node": "*"
576
+
}
577
+
},
578
"node_modules/@eslint/js": {
579
+
"version": "9.39.1",
580
"dev": true,
581
"license": "MIT",
582
"engines": {
···
587
}
588
},
589
"node_modules/@eslint/object-schema": {
590
+
"version": "2.1.7",
591
"dev": true,
592
"license": "Apache-2.0",
593
"engines": {
···
595
}
596
},
597
"node_modules/@eslint/plugin-kit": {
598
+
"version": "0.4.1",
599
"dev": true,
600
"license": "Apache-2.0",
601
"dependencies": {
602
+
"@eslint/core": "^0.17.0",
603
"levn": "^0.4.1"
604
},
605
"engines": {
···
608
},
609
"node_modules/@humanfs/core": {
610
"version": "0.19.1",
611
"dev": true,
612
"license": "Apache-2.0",
613
"engines": {
···
616
},
617
"node_modules/@humanfs/node": {
618
"version": "0.16.7",
619
"dev": true,
620
"license": "Apache-2.0",
621
"dependencies": {
···
628
},
629
"node_modules/@humanwhocodes/module-importer": {
630
"version": "1.0.1",
631
"dev": true,
632
"license": "Apache-2.0",
633
"engines": {
···
640
},
641
"node_modules/@humanwhocodes/retry": {
642
"version": "0.4.3",
643
"dev": true,
644
"license": "Apache-2.0",
645
"engines": {
···
652
},
653
"node_modules/@isaacs/balanced-match": {
654
"version": "4.0.1",
655
"dev": true,
656
"license": "MIT",
657
"engines": {
···
660
},
661
"node_modules/@isaacs/brace-expansion": {
662
"version": "5.0.0",
663
"dev": true,
664
"license": "MIT",
665
"dependencies": {
···
671
},
672
"node_modules/@jridgewell/gen-mapping": {
673
"version": "0.3.13",
674
"dev": true,
675
"license": "MIT",
676
"dependencies": {
···
680
},
681
"node_modules/@jridgewell/remapping": {
682
"version": "2.3.5",
683
"dev": true,
684
"license": "MIT",
685
"dependencies": {
···
689
},
690
"node_modules/@jridgewell/resolve-uri": {
691
"version": "3.1.2",
692
"dev": true,
693
"license": "MIT",
694
"engines": {
···
697
},
698
"node_modules/@jridgewell/source-map": {
699
"version": "0.3.11",
700
"dev": true,
701
"license": "MIT",
702
"optional": true,
703
"dependencies": {
704
"@jridgewell/gen-mapping": "^0.3.5",
705
"@jridgewell/trace-mapping": "^0.3.25"
···
707
},
708
"node_modules/@jridgewell/sourcemap-codec": {
709
"version": "1.5.5",
710
"dev": true,
711
"license": "MIT"
712
},
713
"node_modules/@jridgewell/trace-mapping": {
714
"version": "0.3.31",
715
"dev": true,
716
"license": "MIT",
717
"dependencies": {
···
720
}
721
},
722
"node_modules/@microsoft/api-extractor": {
723
+
"version": "7.55.1",
724
"dev": true,
725
"license": "MIT",
726
"dependencies": {
727
+
"@microsoft/api-extractor-model": "7.32.1",
728
+
"@microsoft/tsdoc": "~0.16.0",
729
+
"@microsoft/tsdoc-config": "~0.18.0",
730
+
"@rushstack/node-core-library": "5.19.0",
731
"@rushstack/rig-package": "0.6.0",
732
+
"@rushstack/terminal": "0.19.4",
733
+
"@rushstack/ts-command-line": "5.1.4",
734
+
"diff": "~8.0.2",
735
"lodash": "~4.17.15",
736
"minimatch": "10.0.3",
737
"resolve": "~1.22.1",
···
744
}
745
},
746
"node_modules/@microsoft/api-extractor-model": {
747
+
"version": "7.32.1",
748
"dev": true,
749
"license": "MIT",
750
"dependencies": {
751
+
"@microsoft/tsdoc": "~0.16.0",
752
+
"@microsoft/tsdoc-config": "~0.18.0",
753
+
"@rushstack/node-core-library": "5.19.0"
754
}
755
},
756
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
757
"version": "5.8.2",
758
"dev": true,
759
"license": "Apache-2.0",
760
"bin": {
···
765
"node": ">=14.17"
766
}
767
},
768
"node_modules/@microsoft/tsdoc": {
769
+
"version": "0.16.0",
770
"dev": true,
771
"license": "MIT"
772
},
773
"node_modules/@microsoft/tsdoc-config": {
774
+
"version": "0.18.0",
775
"dev": true,
776
"license": "MIT",
777
"dependencies": {
778
+
"@microsoft/tsdoc": "0.16.0",
779
"ajv": "~8.12.0",
780
"jju": "~1.4.0",
781
"resolve": "~1.22.2"
···
783
},
784
"node_modules/@microsoft/tsdoc-config/node_modules/ajv": {
785
"version": "8.12.0",
786
"dev": true,
787
"license": "MIT",
788
"dependencies": {
···
796
"url": "https://github.com/sponsors/epoberezkin"
797
}
798
},
799
+
"node_modules/@napi-rs/wasm-runtime": {
800
+
"version": "1.1.0",
801
+
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz",
802
+
"integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==",
803
+
"dev": true,
804
+
"license": "MIT",
805
+
"optional": true,
806
+
"dependencies": {
807
+
"@emnapi/core": "^1.7.1",
808
+
"@emnapi/runtime": "^1.7.1",
809
+
"@tybys/wasm-util": "^0.10.1"
810
+
}
811
+
},
812
+
"node_modules/@oxc-project/runtime": {
813
+
"version": "0.92.0",
814
+
"dev": true,
815
+
"license": "MIT",
816
+
"engines": {
817
+
"node": "^20.19.0 || >=22.12.0"
818
+
}
819
+
},
820
+
"node_modules/@oxc-project/types": {
821
+
"version": "0.93.0",
822
+
"dev": true,
823
+
"license": "MIT",
824
+
"funding": {
825
+
"url": "https://github.com/sponsors/Boshen"
826
+
}
827
+
},
828
+
"node_modules/@rolldown/binding-android-arm64": {
829
+
"version": "1.0.0-beta.41",
830
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz",
831
+
"integrity": "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==",
832
+
"cpu": [
833
+
"arm64"
834
+
],
835
+
"dev": true,
836
+
"license": "MIT",
837
+
"optional": true,
838
+
"os": [
839
+
"android"
840
+
],
841
+
"engines": {
842
+
"node": "^20.19.0 || >=22.12.0"
843
+
}
844
+
},
845
+
"node_modules/@rolldown/binding-darwin-arm64": {
846
+
"version": "1.0.0-beta.41",
847
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz",
848
+
"integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==",
849
+
"cpu": [
850
+
"arm64"
851
+
],
852
+
"dev": true,
853
+
"license": "MIT",
854
+
"optional": true,
855
+
"os": [
856
+
"darwin"
857
+
],
858
+
"engines": {
859
+
"node": "^20.19.0 || >=22.12.0"
860
+
}
861
+
},
862
+
"node_modules/@rolldown/binding-darwin-x64": {
863
+
"version": "1.0.0-beta.41",
864
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.41.tgz",
865
+
"integrity": "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==",
866
+
"cpu": [
867
+
"x64"
868
+
],
869
+
"dev": true,
870
+
"license": "MIT",
871
+
"optional": true,
872
+
"os": [
873
+
"darwin"
874
+
],
875
+
"engines": {
876
+
"node": "^20.19.0 || >=22.12.0"
877
+
}
878
+
},
879
+
"node_modules/@rolldown/binding-freebsd-x64": {
880
+
"version": "1.0.0-beta.41",
881
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.41.tgz",
882
+
"integrity": "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==",
883
+
"cpu": [
884
+
"x64"
885
+
],
886
+
"dev": true,
887
+
"license": "MIT",
888
+
"optional": true,
889
+
"os": [
890
+
"freebsd"
891
+
],
892
+
"engines": {
893
+
"node": "^20.19.0 || >=22.12.0"
894
+
}
895
+
},
896
+
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
897
+
"version": "1.0.0-beta.41",
898
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.41.tgz",
899
+
"integrity": "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==",
900
+
"cpu": [
901
+
"arm"
902
+
],
903
+
"dev": true,
904
+
"license": "MIT",
905
+
"optional": true,
906
+
"os": [
907
+
"linux"
908
+
],
909
+
"engines": {
910
+
"node": "^20.19.0 || >=22.12.0"
911
+
}
912
+
},
913
+
"node_modules/@rolldown/binding-linux-arm64-gnu": {
914
+
"version": "1.0.0-beta.41",
915
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.41.tgz",
916
+
"integrity": "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==",
917
+
"cpu": [
918
+
"arm64"
919
+
],
920
+
"dev": true,
921
+
"license": "MIT",
922
+
"optional": true,
923
+
"os": [
924
+
"linux"
925
+
],
926
+
"engines": {
927
+
"node": "^20.19.0 || >=22.12.0"
928
+
}
929
+
},
930
+
"node_modules/@rolldown/binding-linux-arm64-musl": {
931
+
"version": "1.0.0-beta.41",
932
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.41.tgz",
933
+
"integrity": "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==",
934
+
"cpu": [
935
+
"arm64"
936
+
],
937
"dev": true,
938
+
"license": "MIT",
939
+
"optional": true,
940
+
"os": [
941
+
"linux"
942
+
],
943
+
"engines": {
944
+
"node": "^20.19.0 || >=22.12.0"
945
+
}
946
},
947
+
"node_modules/@rolldown/binding-linux-x64-gnu": {
948
+
"version": "1.0.0-beta.41",
949
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.41.tgz",
950
+
"integrity": "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==",
951
+
"cpu": [
952
+
"x64"
953
+
],
954
"dev": true,
955
"license": "MIT",
956
+
"optional": true,
957
+
"os": [
958
+
"linux"
959
+
],
960
"engines": {
961
+
"node": "^20.19.0 || >=22.12.0"
962
}
963
},
964
+
"node_modules/@rolldown/binding-linux-x64-musl": {
965
+
"version": "1.0.0-beta.41",
966
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.41.tgz",
967
+
"integrity": "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==",
968
+
"cpu": [
969
+
"x64"
970
+
],
971
"dev": true,
972
"license": "MIT",
973
+
"optional": true,
974
+
"os": [
975
+
"linux"
976
+
],
977
"engines": {
978
+
"node": "^20.19.0 || >=22.12.0"
979
}
980
},
981
+
"node_modules/@rolldown/binding-openharmony-arm64": {
982
+
"version": "1.0.0-beta.41",
983
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.41.tgz",
984
+
"integrity": "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==",
985
+
"cpu": [
986
+
"arm64"
987
+
],
988
+
"dev": true,
989
+
"license": "MIT",
990
+
"optional": true,
991
+
"os": [
992
+
"openharmony"
993
+
],
994
+
"engines": {
995
+
"node": "^20.19.0 || >=22.12.0"
996
+
}
997
+
},
998
+
"node_modules/@rolldown/binding-wasm32-wasi": {
999
+
"version": "1.0.0-beta.41",
1000
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.41.tgz",
1001
+
"integrity": "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==",
1002
+
"cpu": [
1003
+
"wasm32"
1004
+
],
1005
"dev": true,
1006
"license": "MIT",
1007
+
"optional": true,
1008
"dependencies": {
1009
+
"@napi-rs/wasm-runtime": "^1.0.5"
1010
},
1011
"engines": {
1012
+
"node": ">=14.0.0"
1013
}
1014
},
1015
+
"node_modules/@rolldown/binding-win32-arm64-msvc": {
1016
+
"version": "1.0.0-beta.41",
1017
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz",
1018
+
"integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==",
1019
+
"cpu": [
1020
+
"arm64"
1021
+
],
1022
"dev": true,
1023
"license": "MIT",
1024
+
"optional": true,
1025
+
"os": [
1026
+
"win32"
1027
+
],
1028
"engines": {
1029
"node": "^20.19.0 || >=22.12.0"
1030
}
1031
},
1032
+
"node_modules/@rolldown/binding-win32-ia32-msvc": {
1033
+
"version": "1.0.0-beta.41",
1034
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz",
1035
+
"integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==",
1036
+
"cpu": [
1037
+
"ia32"
1038
+
],
1039
"dev": true,
1040
"license": "MIT",
1041
+
"optional": true,
1042
+
"os": [
1043
+
"win32"
1044
+
],
1045
+
"engines": {
1046
+
"node": "^20.19.0 || >=22.12.0"
1047
}
1048
},
1049
+
"node_modules/@rolldown/binding-win32-x64-msvc": {
1050
"version": "1.0.0-beta.41",
1051
"cpu": [
1052
+
"x64"
1053
],
1054
"dev": true,
1055
"license": "MIT",
1056
"optional": true,
1057
"os": [
1058
+
"win32"
1059
],
1060
"engines": {
1061
"node": "^20.19.0 || >=22.12.0"
1062
}
1063
},
1064
"node_modules/@rolldown/pluginutils": {
1065
+
"version": "1.0.0-beta.47",
1066
"dev": true,
1067
"license": "MIT"
1068
},
1069
"node_modules/@rollup/pluginutils": {
1070
+
"version": "4.2.1",
1071
"dev": true,
1072
"license": "MIT",
1073
"dependencies": {
1074
+
"estree-walker": "^2.0.1",
1075
+
"picomatch": "^2.2.2"
1076
},
1077
"engines": {
1078
+
"node": ">= 8.0.0"
1079
}
1080
},
1081
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
1082
+
"version": "2.3.1",
1083
"dev": true,
1084
"license": "MIT",
1085
"engines": {
1086
+
"node": ">=8.6"
1087
},
1088
"funding": {
1089
"url": "https://github.com/sponsors/jonschlinkert"
1090
}
1091
},
1092
+
"node_modules/@rollup/rollup-android-arm-eabi": {
1093
+
"version": "4.53.3",
1094
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
1095
+
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
1096
+
"cpu": [
1097
+
"arm"
1098
+
],
1099
+
"dev": true,
1100
+
"license": "MIT",
1101
+
"optional": true,
1102
+
"os": [
1103
+
"android"
1104
+
]
1105
+
},
1106
+
"node_modules/@rollup/rollup-android-arm64": {
1107
+
"version": "4.53.3",
1108
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
1109
+
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
1110
+
"cpu": [
1111
+
"arm64"
1112
+
],
1113
+
"dev": true,
1114
+
"license": "MIT",
1115
+
"optional": true,
1116
+
"os": [
1117
+
"android"
1118
+
]
1119
+
},
1120
"node_modules/@rollup/rollup-darwin-arm64": {
1121
+
"version": "4.53.3",
1122
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
1123
+
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
1124
"cpu": [
1125
"arm64"
1126
],
···
1129
"optional": true,
1130
"os": [
1131
"darwin"
1132
+
]
1133
+
},
1134
+
"node_modules/@rollup/rollup-darwin-x64": {
1135
+
"version": "4.53.3",
1136
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
1137
+
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
1138
+
"cpu": [
1139
+
"x64"
1140
],
1141
+
"dev": true,
1142
+
"license": "MIT",
1143
+
"optional": true,
1144
+
"os": [
1145
+
"darwin"
1146
+
]
1147
+
},
1148
+
"node_modules/@rollup/rollup-freebsd-arm64": {
1149
+
"version": "4.53.3",
1150
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
1151
+
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
1152
+
"cpu": [
1153
+
"arm64"
1154
+
],
1155
+
"dev": true,
1156
+
"license": "MIT",
1157
+
"optional": true,
1158
+
"os": [
1159
+
"freebsd"
1160
+
]
1161
+
},
1162
+
"node_modules/@rollup/rollup-freebsd-x64": {
1163
+
"version": "4.53.3",
1164
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
1165
+
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
1166
+
"cpu": [
1167
+
"x64"
1168
+
],
1169
+
"dev": true,
1170
+
"license": "MIT",
1171
+
"optional": true,
1172
+
"os": [
1173
+
"freebsd"
1174
+
]
1175
+
},
1176
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1177
+
"version": "4.53.3",
1178
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
1179
+
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
1180
+
"cpu": [
1181
+
"arm"
1182
+
],
1183
+
"dev": true,
1184
+
"license": "MIT",
1185
+
"optional": true,
1186
+
"os": [
1187
+
"linux"
1188
+
]
1189
+
},
1190
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
1191
+
"version": "4.53.3",
1192
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
1193
+
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
1194
+
"cpu": [
1195
+
"arm"
1196
+
],
1197
+
"dev": true,
1198
+
"license": "MIT",
1199
+
"optional": true,
1200
+
"os": [
1201
+
"linux"
1202
+
]
1203
+
},
1204
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
1205
+
"version": "4.53.3",
1206
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
1207
+
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
1208
+
"cpu": [
1209
+
"arm64"
1210
+
],
1211
+
"dev": true,
1212
+
"license": "MIT",
1213
+
"optional": true,
1214
+
"os": [
1215
+
"linux"
1216
+
]
1217
+
},
1218
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
1219
+
"version": "4.53.3",
1220
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
1221
+
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
1222
+
"cpu": [
1223
+
"arm64"
1224
+
],
1225
+
"dev": true,
1226
+
"license": "MIT",
1227
+
"optional": true,
1228
+
"os": [
1229
+
"linux"
1230
+
]
1231
+
},
1232
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
1233
+
"version": "4.53.3",
1234
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
1235
+
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
1236
+
"cpu": [
1237
+
"loong64"
1238
+
],
1239
+
"dev": true,
1240
+
"license": "MIT",
1241
+
"optional": true,
1242
+
"os": [
1243
+
"linux"
1244
+
]
1245
+
},
1246
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
1247
+
"version": "4.53.3",
1248
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
1249
+
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
1250
+
"cpu": [
1251
+
"ppc64"
1252
+
],
1253
+
"dev": true,
1254
+
"license": "MIT",
1255
+
"optional": true,
1256
+
"os": [
1257
+
"linux"
1258
+
]
1259
+
},
1260
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
1261
+
"version": "4.53.3",
1262
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
1263
+
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
1264
+
"cpu": [
1265
+
"riscv64"
1266
+
],
1267
+
"dev": true,
1268
+
"license": "MIT",
1269
+
"optional": true,
1270
+
"os": [
1271
+
"linux"
1272
+
]
1273
+
},
1274
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
1275
+
"version": "4.53.3",
1276
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
1277
+
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
1278
+
"cpu": [
1279
+
"riscv64"
1280
+
],
1281
+
"dev": true,
1282
+
"license": "MIT",
1283
+
"optional": true,
1284
+
"os": [
1285
+
"linux"
1286
+
]
1287
+
},
1288
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
1289
+
"version": "4.53.3",
1290
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
1291
+
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
1292
+
"cpu": [
1293
+
"s390x"
1294
+
],
1295
+
"dev": true,
1296
+
"license": "MIT",
1297
+
"optional": true,
1298
+
"os": [
1299
+
"linux"
1300
+
]
1301
+
},
1302
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
1303
+
"version": "4.53.3",
1304
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
1305
+
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
1306
+
"cpu": [
1307
+
"x64"
1308
+
],
1309
+
"dev": true,
1310
+
"license": "MIT",
1311
+
"optional": true,
1312
+
"os": [
1313
+
"linux"
1314
+
]
1315
+
},
1316
+
"node_modules/@rollup/rollup-linux-x64-musl": {
1317
+
"version": "4.53.3",
1318
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
1319
+
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
1320
+
"cpu": [
1321
+
"x64"
1322
+
],
1323
+
"dev": true,
1324
+
"license": "MIT",
1325
+
"optional": true,
1326
+
"os": [
1327
+
"linux"
1328
+
]
1329
+
},
1330
+
"node_modules/@rollup/rollup-openharmony-arm64": {
1331
+
"version": "4.53.3",
1332
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
1333
+
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
1334
+
"cpu": [
1335
+
"arm64"
1336
+
],
1337
+
"dev": true,
1338
+
"license": "MIT",
1339
+
"optional": true,
1340
+
"os": [
1341
+
"openharmony"
1342
+
]
1343
+
},
1344
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
1345
+
"version": "4.53.3",
1346
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
1347
+
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
1348
+
"cpu": [
1349
+
"arm64"
1350
+
],
1351
+
"dev": true,
1352
+
"license": "MIT",
1353
+
"optional": true,
1354
+
"os": [
1355
+
"win32"
1356
+
]
1357
+
},
1358
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
1359
+
"version": "4.53.3",
1360
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
1361
+
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
1362
+
"cpu": [
1363
+
"ia32"
1364
+
],
1365
+
"dev": true,
1366
+
"license": "MIT",
1367
+
"optional": true,
1368
+
"os": [
1369
+
"win32"
1370
+
]
1371
+
},
1372
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
1373
+
"version": "4.53.3",
1374
+
"cpu": [
1375
+
"x64"
1376
+
],
1377
+
"dev": true,
1378
+
"license": "MIT",
1379
+
"optional": true,
1380
+
"os": [
1381
+
"win32"
1382
+
]
1383
+
},
1384
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
1385
+
"version": "4.53.3",
1386
+
"cpu": [
1387
+
"x64"
1388
+
],
1389
+
"dev": true,
1390
+
"license": "MIT",
1391
+
"optional": true,
1392
+
"os": [
1393
+
"win32"
1394
+
]
1395
},
1396
"node_modules/@rushstack/node-core-library": {
1397
+
"version": "5.19.0",
1398
"dev": true,
1399
"license": "MIT",
1400
"dependencies": {
···
1418
},
1419
"node_modules/@rushstack/node-core-library/node_modules/ajv": {
1420
"version": "8.13.0",
1421
"dev": true,
1422
"license": "MIT",
1423
"dependencies": {
···
1431
"url": "https://github.com/sponsors/epoberezkin"
1432
}
1433
},
1434
+
"node_modules/@rushstack/node-core-library/node_modules/fs-extra": {
1435
+
"version": "11.3.2",
1436
"dev": true,
1437
"license": "MIT",
1438
"dependencies": {
1439
+
"graceful-fs": "^4.2.0",
1440
+
"jsonfile": "^6.0.1",
1441
+
"universalify": "^2.0.0"
1442
},
1443
"engines": {
1444
+
"node": ">=14.14"
1445
}
1446
},
1447
"node_modules/@rushstack/problem-matcher": {
1448
"version": "0.1.1",
1449
"dev": true,
1450
"license": "MIT",
1451
"peerDependencies": {
···
1459
},
1460
"node_modules/@rushstack/rig-package": {
1461
"version": "0.6.0",
1462
"dev": true,
1463
"license": "MIT",
1464
"dependencies": {
···
1467
}
1468
},
1469
"node_modules/@rushstack/terminal": {
1470
+
"version": "0.19.4",
1471
"dev": true,
1472
"license": "MIT",
1473
"dependencies": {
1474
+
"@rushstack/node-core-library": "5.19.0",
1475
"@rushstack/problem-matcher": "0.1.1",
1476
"supports-color": "~8.1.1"
1477
},
···
1484
}
1485
}
1486
},
1487
"node_modules/@rushstack/ts-command-line": {
1488
+
"version": "5.1.4",
1489
"dev": true,
1490
"license": "MIT",
1491
"dependencies": {
1492
+
"@rushstack/terminal": "0.19.4",
1493
"@types/argparse": "1.0.38",
1494
"argparse": "~1.0.9",
1495
"string-argv": "~0.3.1"
1496
}
1497
},
1498
+
"node_modules/@standard-schema/spec": {
1499
+
"version": "1.0.0",
1500
+
"license": "MIT"
1501
+
},
1502
+
"node_modules/@tybys/wasm-util": {
1503
+
"version": "0.10.1",
1504
+
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
1505
+
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
1506
"dev": true,
1507
"license": "MIT",
1508
+
"optional": true,
1509
"dependencies": {
1510
+
"tslib": "^2.4.0"
1511
}
1512
},
1513
"node_modules/@types/argparse": {
1514
"version": "1.0.38",
1515
"dev": true,
1516
"license": "MIT"
1517
},
1518
"node_modules/@types/babel__core": {
1519
"version": "7.20.5",
1520
"dev": true,
1521
"license": "MIT",
1522
"dependencies": {
···
1529
},
1530
"node_modules/@types/babel__generator": {
1531
"version": "7.27.0",
1532
"dev": true,
1533
"license": "MIT",
1534
"dependencies": {
···
1537
},
1538
"node_modules/@types/babel__template": {
1539
"version": "7.4.4",
1540
"dev": true,
1541
"license": "MIT",
1542
"dependencies": {
···
1546
},
1547
"node_modules/@types/babel__traverse": {
1548
"version": "7.28.0",
1549
"dev": true,
1550
"license": "MIT",
1551
"dependencies": {
···
1554
},
1555
"node_modules/@types/estree": {
1556
"version": "1.0.8",
1557
"dev": true,
1558
"license": "MIT"
1559
},
1560
"node_modules/@types/json-schema": {
1561
"version": "7.0.15",
1562
"dev": true,
1563
"license": "MIT"
1564
},
1565
"node_modules/@types/node": {
1566
+
"version": "24.10.1",
1567
"dev": true,
1568
"license": "MIT",
1569
+
"peer": true,
1570
"dependencies": {
1571
+
"undici-types": "~7.16.0"
1572
}
1573
},
1574
"node_modules/@types/react": {
1575
+
"version": "19.2.7",
1576
"dev": true,
1577
"license": "MIT",
1578
+
"peer": true,
1579
"dependencies": {
1580
+
"csstype": "^3.2.2"
1581
}
1582
},
1583
"node_modules/@types/react-dom": {
1584
+
"version": "19.2.3",
1585
"dev": true,
1586
"license": "MIT",
1587
"peerDependencies": {
···
1589
}
1590
},
1591
"node_modules/@typescript-eslint/eslint-plugin": {
1592
+
"version": "8.48.1",
1593
"dev": true,
1594
"license": "MIT",
1595
"dependencies": {
1596
"@eslint-community/regexpp": "^4.10.0",
1597
+
"@typescript-eslint/scope-manager": "8.48.1",
1598
+
"@typescript-eslint/type-utils": "8.48.1",
1599
+
"@typescript-eslint/utils": "8.48.1",
1600
+
"@typescript-eslint/visitor-keys": "8.48.1",
1601
"graphemer": "^1.4.0",
1602
"ignore": "^7.0.0",
1603
"natural-compare": "^1.4.0",
···
1611
"url": "https://opencollective.com/typescript-eslint"
1612
},
1613
"peerDependencies": {
1614
+
"@typescript-eslint/parser": "^8.48.1",
1615
"eslint": "^8.57.0 || ^9.0.0",
1616
"typescript": ">=4.8.4 <6.0.0"
1617
}
1618
},
1619
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
1620
"version": "7.0.5",
1621
"dev": true,
1622
"license": "MIT",
1623
"engines": {
···
1625
}
1626
},
1627
"node_modules/@typescript-eslint/parser": {
1628
+
"version": "8.48.1",
1629
"dev": true,
1630
"license": "MIT",
1631
+
"peer": true,
1632
"dependencies": {
1633
+
"@typescript-eslint/scope-manager": "8.48.1",
1634
+
"@typescript-eslint/types": "8.48.1",
1635
+
"@typescript-eslint/typescript-estree": "8.48.1",
1636
+
"@typescript-eslint/visitor-keys": "8.48.1",
1637
"debug": "^4.3.4"
1638
},
1639
"engines": {
···
1649
}
1650
},
1651
"node_modules/@typescript-eslint/project-service": {
1652
+
"version": "8.48.1",
1653
"dev": true,
1654
"license": "MIT",
1655
"dependencies": {
1656
+
"@typescript-eslint/tsconfig-utils": "^8.48.1",
1657
+
"@typescript-eslint/types": "^8.48.1",
1658
"debug": "^4.3.4"
1659
},
1660
"engines": {
···
1669
}
1670
},
1671
"node_modules/@typescript-eslint/scope-manager": {
1672
+
"version": "8.48.1",
1673
"dev": true,
1674
"license": "MIT",
1675
"dependencies": {
1676
+
"@typescript-eslint/types": "8.48.1",
1677
+
"@typescript-eslint/visitor-keys": "8.48.1"
1678
},
1679
"engines": {
1680
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1685
}
1686
},
1687
"node_modules/@typescript-eslint/tsconfig-utils": {
1688
+
"version": "8.48.1",
1689
"dev": true,
1690
"license": "MIT",
1691
"engines": {
···
1700
}
1701
},
1702
"node_modules/@typescript-eslint/type-utils": {
1703
+
"version": "8.48.1",
1704
"dev": true,
1705
"license": "MIT",
1706
"dependencies": {
1707
+
"@typescript-eslint/types": "8.48.1",
1708
+
"@typescript-eslint/typescript-estree": "8.48.1",
1709
+
"@typescript-eslint/utils": "8.48.1",
1710
"debug": "^4.3.4",
1711
"ts-api-utils": "^2.1.0"
1712
},
···
1723
}
1724
},
1725
"node_modules/@typescript-eslint/types": {
1726
+
"version": "8.48.1",
1727
"dev": true,
1728
"license": "MIT",
1729
"engines": {
···
1735
}
1736
},
1737
"node_modules/@typescript-eslint/typescript-estree": {
1738
+
"version": "8.48.1",
1739
"dev": true,
1740
"license": "MIT",
1741
"dependencies": {
1742
+
"@typescript-eslint/project-service": "8.48.1",
1743
+
"@typescript-eslint/tsconfig-utils": "8.48.1",
1744
+
"@typescript-eslint/types": "8.48.1",
1745
+
"@typescript-eslint/visitor-keys": "8.48.1",
1746
"debug": "^4.3.4",
1747
"minimatch": "^9.0.4",
1748
"semver": "^7.6.0",
1749
+
"tinyglobby": "^0.2.15",
1750
"ts-api-utils": "^2.1.0"
1751
},
1752
"engines": {
···
1760
"typescript": ">=4.8.4 <6.0.0"
1761
}
1762
},
1763
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
1764
"version": "9.0.5",
1765
"dev": true,
1766
"license": "ISC",
1767
"dependencies": {
···
1774
"url": "https://github.com/sponsors/isaacs"
1775
}
1776
},
1777
+
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": {
1778
+
"version": "2.0.2",
1779
+
"dev": true,
1780
+
"license": "MIT",
1781
+
"dependencies": {
1782
+
"balanced-match": "^1.0.0"
1783
+
}
1784
+
},
1785
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
1786
"version": "7.7.3",
1787
"dev": true,
1788
"license": "ISC",
1789
"bin": {
···
1794
}
1795
},
1796
"node_modules/@typescript-eslint/utils": {
1797
+
"version": "8.48.1",
1798
"dev": true,
1799
"license": "MIT",
1800
"dependencies": {
1801
"@eslint-community/eslint-utils": "^4.7.0",
1802
+
"@typescript-eslint/scope-manager": "8.48.1",
1803
+
"@typescript-eslint/types": "8.48.1",
1804
+
"@typescript-eslint/typescript-estree": "8.48.1"
1805
},
1806
"engines": {
1807
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1816
}
1817
},
1818
"node_modules/@typescript-eslint/visitor-keys": {
1819
+
"version": "8.48.1",
1820
"dev": true,
1821
"license": "MIT",
1822
"dependencies": {
1823
+
"@typescript-eslint/types": "8.48.1",
1824
"eslint-visitor-keys": "^4.2.1"
1825
},
1826
"engines": {
···
1832
}
1833
},
1834
"node_modules/@vitejs/plugin-react": {
1835
+
"version": "5.1.1",
1836
"dev": true,
1837
"license": "MIT",
1838
"dependencies": {
1839
+
"@babel/core": "^7.28.5",
1840
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
1841
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
1842
+
"@rolldown/pluginutils": "1.0.0-beta.47",
1843
"@types/babel__core": "^7.20.5",
1844
+
"react-refresh": "^0.18.0"
1845
},
1846
"engines": {
1847
"node": "^20.19.0 || >=22.12.0"
···
1851
}
1852
},
1853
"node_modules/@volar/language-core": {
1854
+
"version": "2.4.26",
1855
"dev": true,
1856
"license": "MIT",
1857
"dependencies": {
1858
+
"@volar/source-map": "2.4.26"
1859
}
1860
},
1861
"node_modules/@volar/source-map": {
1862
+
"version": "2.4.26",
1863
"dev": true,
1864
"license": "MIT"
1865
},
1866
"node_modules/@volar/typescript": {
1867
+
"version": "2.4.26",
1868
"dev": true,
1869
"license": "MIT",
1870
"dependencies": {
1871
+
"@volar/language-core": "2.4.26",
1872
"path-browserify": "^1.0.1",
1873
"vscode-uri": "^3.0.8"
1874
}
1875
},
1876
"node_modules/acorn": {
1877
"version": "8.15.0",
1878
"dev": true,
1879
"license": "MIT",
1880
+
"peer": true,
1881
"bin": {
1882
"acorn": "bin/acorn"
1883
},
···
1887
},
1888
"node_modules/acorn-jsx": {
1889
"version": "5.3.2",
1890
"dev": true,
1891
"license": "MIT",
1892
"peerDependencies": {
···
1894
}
1895
},
1896
"node_modules/ajv": {
1897
+
"version": "8.17.1",
1898
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
1899
+
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
1900
"dev": true,
1901
"license": "MIT",
1902
+
"peer": true,
1903
"dependencies": {
1904
+
"fast-deep-equal": "^3.1.3",
1905
+
"fast-uri": "^3.0.1",
1906
+
"json-schema-traverse": "^1.0.0",
1907
+
"require-from-string": "^2.0.2"
1908
},
1909
"funding": {
1910
"type": "github",
1911
"url": "https://github.com/sponsors/epoberezkin"
1912
}
1913
},
1914
+
"node_modules/ajv-draft-04": {
1915
+
"version": "1.0.0",
1916
+
"dev": true,
1917
+
"license": "MIT",
1918
+
"peerDependencies": {
1919
+
"ajv": "^8.5.0"
1920
+
},
1921
+
"peerDependenciesMeta": {
1922
+
"ajv": {
1923
+
"optional": true
1924
+
}
1925
+
}
1926
+
},
1927
"node_modules/ajv-formats": {
1928
"version": "3.0.1",
1929
"dev": true,
1930
"license": "MIT",
1931
"dependencies": {
···
1940
}
1941
}
1942
},
1943
"node_modules/ansi-styles": {
1944
"version": "4.3.0",
1945
"dev": true,
1946
"license": "MIT",
1947
"dependencies": {
···
1956
},
1957
"node_modules/ansis": {
1958
"version": "4.2.0",
1959
"dev": true,
1960
"license": "ISC",
1961
"engines": {
···
1963
}
1964
},
1965
"node_modules/argparse": {
1966
+
"version": "1.0.10",
1967
"dev": true,
1968
+
"license": "MIT",
1969
+
"dependencies": {
1970
+
"sprintf-js": "~1.0.2"
1971
+
}
1972
},
1973
"node_modules/balanced-match": {
1974
"version": "1.0.2",
1975
"dev": true,
1976
"license": "MIT"
1977
},
1978
"node_modules/baseline-browser-mapping": {
1979
+
"version": "2.8.32",
1980
"dev": true,
1981
"license": "Apache-2.0",
1982
"bin": {
···
1985
},
1986
"node_modules/brace-expansion": {
1987
"version": "1.1.12",
1988
"dev": true,
1989
"license": "MIT",
1990
"dependencies": {
···
1992
"concat-map": "0.0.1"
1993
}
1994
},
1995
"node_modules/browserslist": {
1996
+
"version": "4.28.0",
1997
"dev": true,
1998
"funding": [
1999
{
···
2010
}
2011
],
2012
"license": "MIT",
2013
+
"peer": true,
2014
"dependencies": {
2015
+
"baseline-browser-mapping": "^2.8.25",
2016
+
"caniuse-lite": "^1.0.30001754",
2017
+
"electron-to-chromium": "^1.5.249",
2018
+
"node-releases": "^2.0.27",
2019
+
"update-browserslist-db": "^1.1.4"
2020
},
2021
"bin": {
2022
"browserslist": "cli.js"
···
2027
},
2028
"node_modules/buffer-from": {
2029
"version": "1.1.2",
2030
"dev": true,
2031
"license": "MIT",
2032
+
"optional": true
2033
},
2034
"node_modules/callsites": {
2035
"version": "3.1.0",
2036
"dev": true,
2037
"license": "MIT",
2038
"engines": {
···
2040
}
2041
},
2042
"node_modules/caniuse-lite": {
2043
+
"version": "1.0.30001759",
2044
"dev": true,
2045
"funding": [
2046
{
···
2060
},
2061
"node_modules/chalk": {
2062
"version": "4.1.2",
2063
"dev": true,
2064
"license": "MIT",
2065
"dependencies": {
···
2073
"url": "https://github.com/chalk/chalk?sponsor=1"
2074
}
2075
},
2076
+
"node_modules/chalk/node_modules/supports-color": {
2077
+
"version": "7.2.0",
2078
+
"dev": true,
2079
+
"license": "MIT",
2080
+
"dependencies": {
2081
+
"has-flag": "^4.0.0"
2082
+
},
2083
+
"engines": {
2084
+
"node": ">=8"
2085
+
}
2086
+
},
2087
"node_modules/color-convert": {
2088
"version": "2.0.1",
2089
"dev": true,
2090
"license": "MIT",
2091
"dependencies": {
···
2097
},
2098
"node_modules/color-name": {
2099
"version": "1.1.4",
2100
"dev": true,
2101
"license": "MIT"
2102
},
2103
"node_modules/commander": {
2104
"version": "2.20.3",
2105
"dev": true,
2106
"license": "MIT",
2107
+
"optional": true
2108
},
2109
"node_modules/commondir": {
2110
"version": "1.0.1",
2111
"dev": true,
2112
"license": "MIT"
2113
},
2114
"node_modules/compare-versions": {
2115
"version": "6.1.1",
2116
"dev": true,
2117
"license": "MIT"
2118
},
2119
"node_modules/concat-map": {
2120
"version": "0.0.1",
2121
"dev": true,
2122
"license": "MIT"
2123
},
2124
"node_modules/confbox": {
2125
"version": "0.2.2",
2126
"dev": true,
2127
"license": "MIT"
2128
},
2129
"node_modules/convert-source-map": {
2130
"version": "2.0.0",
2131
"dev": true,
2132
"license": "MIT"
2133
},
2134
"node_modules/cross-spawn": {
2135
"version": "7.0.6",
2136
"dev": true,
2137
"license": "MIT",
2138
"dependencies": {
···
2145
}
2146
},
2147
"node_modules/csstype": {
2148
+
"version": "3.2.3",
2149
"dev": true,
2150
"license": "MIT"
2151
},
2152
"node_modules/debug": {
2153
"version": "4.4.3",
2154
"dev": true,
2155
"license": "MIT",
2156
"dependencies": {
···
2167
},
2168
"node_modules/deep-is": {
2169
"version": "0.1.4",
2170
"dev": true,
2171
"license": "MIT"
2172
},
2173
"node_modules/detect-libc": {
2174
"version": "2.1.2",
2175
"dev": true,
2176
"license": "Apache-2.0",
2177
"engines": {
2178
"node": ">=8"
2179
}
2180
},
2181
+
"node_modules/diff": {
2182
+
"version": "8.0.2",
2183
+
"dev": true,
2184
+
"license": "BSD-3-Clause",
2185
+
"engines": {
2186
+
"node": ">=0.3.1"
2187
+
}
2188
+
},
2189
"node_modules/electron-to-chromium": {
2190
+
"version": "1.5.263",
2191
"dev": true,
2192
"license": "ISC"
2193
},
2194
"node_modules/escalade": {
2195
"version": "3.2.0",
2196
"dev": true,
2197
"license": "MIT",
2198
"engines": {
···
2201
},
2202
"node_modules/escape-string-regexp": {
2203
"version": "4.0.0",
2204
"dev": true,
2205
"license": "MIT",
2206
"engines": {
···
2211
}
2212
},
2213
"node_modules/eslint": {
2214
+
"version": "9.39.1",
2215
"dev": true,
2216
"license": "MIT",
2217
+
"peer": true,
2218
"dependencies": {
2219
"@eslint-community/eslint-utils": "^4.8.0",
2220
"@eslint-community/regexpp": "^4.12.1",
2221
+
"@eslint/config-array": "^0.21.1",
2222
+
"@eslint/config-helpers": "^0.4.2",
2223
+
"@eslint/core": "^0.17.0",
2224
"@eslint/eslintrc": "^3.3.1",
2225
+
"@eslint/js": "9.39.1",
2226
+
"@eslint/plugin-kit": "^0.4.1",
2227
"@humanfs/node": "^0.16.6",
2228
"@humanwhocodes/module-importer": "^1.0.1",
2229
"@humanwhocodes/retry": "^0.4.2",
2230
"@types/estree": "^1.0.6",
2231
"ajv": "^6.12.4",
2232
"chalk": "^4.0.0",
2233
"cross-spawn": "^7.0.6",
···
2271
},
2272
"node_modules/eslint-plugin-react-hooks": {
2273
"version": "5.2.0",
2274
"dev": true,
2275
"license": "MIT",
2276
"engines": {
···
2281
}
2282
},
2283
"node_modules/eslint-plugin-react-refresh": {
2284
+
"version": "0.4.24",
2285
"dev": true,
2286
"license": "MIT",
2287
"peerDependencies": {
···
2290
},
2291
"node_modules/eslint-scope": {
2292
"version": "8.4.0",
2293
"dev": true,
2294
"license": "BSD-2-Clause",
2295
"dependencies": {
···
2305
},
2306
"node_modules/eslint-visitor-keys": {
2307
"version": "4.2.1",
2308
"dev": true,
2309
"license": "Apache-2.0",
2310
"engines": {
···
2314
"url": "https://opencollective.com/eslint"
2315
}
2316
},
2317
+
"node_modules/eslint/node_modules/ajv": {
2318
+
"version": "6.12.6",
2319
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
2320
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
2321
+
"dev": true,
2322
+
"license": "MIT",
2323
+
"dependencies": {
2324
+
"fast-deep-equal": "^3.1.1",
2325
+
"fast-json-stable-stringify": "^2.0.0",
2326
+
"json-schema-traverse": "^0.4.1",
2327
+
"uri-js": "^4.2.2"
2328
+
},
2329
+
"funding": {
2330
+
"type": "github",
2331
+
"url": "https://github.com/sponsors/epoberezkin"
2332
+
}
2333
+
},
2334
+
"node_modules/eslint/node_modules/json-schema-traverse": {
2335
+
"version": "0.4.1",
2336
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
2337
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
2338
+
"dev": true,
2339
+
"license": "MIT"
2340
+
},
2341
+
"node_modules/eslint/node_modules/minimatch": {
2342
+
"version": "3.1.2",
2343
+
"dev": true,
2344
+
"license": "ISC",
2345
+
"dependencies": {
2346
+
"brace-expansion": "^1.1.7"
2347
+
},
2348
+
"engines": {
2349
+
"node": "*"
2350
+
}
2351
+
},
2352
"node_modules/esm-env": {
2353
"version": "1.2.2",
2354
"license": "MIT"
2355
},
2356
"node_modules/espree": {
2357
"version": "10.4.0",
2358
"dev": true,
2359
"license": "BSD-2-Clause",
2360
"dependencies": {
···
2371
},
2372
"node_modules/esquery": {
2373
"version": "1.6.0",
2374
"dev": true,
2375
"license": "BSD-3-Clause",
2376
"dependencies": {
···
2382
},
2383
"node_modules/esrecurse": {
2384
"version": "4.3.0",
2385
"dev": true,
2386
"license": "BSD-2-Clause",
2387
"dependencies": {
···
2393
},
2394
"node_modules/estraverse": {
2395
"version": "5.3.0",
2396
"dev": true,
2397
"license": "BSD-2-Clause",
2398
"engines": {
···
2401
},
2402
"node_modules/estree-walker": {
2403
"version": "2.0.2",
2404
"dev": true,
2405
"license": "MIT"
2406
},
2407
"node_modules/esutils": {
2408
"version": "2.0.3",
2409
"dev": true,
2410
"license": "BSD-2-Clause",
2411
"engines": {
···
2413
}
2414
},
2415
"node_modules/exsolve": {
2416
+
"version": "1.0.8",
2417
"dev": true,
2418
"license": "MIT"
2419
},
2420
"node_modules/fast-deep-equal": {
2421
"version": "3.1.3",
2422
"dev": true,
2423
"license": "MIT"
2424
},
2425
"node_modules/fast-json-stable-stringify": {
2426
"version": "2.1.0",
2427
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
···
2431
},
2432
"node_modules/fast-levenshtein": {
2433
"version": "2.0.6",
2434
"dev": true,
2435
"license": "MIT"
2436
},
2437
"node_modules/fast-uri": {
2438
"version": "3.1.0",
2439
"dev": true,
2440
"funding": [
2441
{
···
2449
],
2450
"license": "BSD-3-Clause"
2451
},
2452
+
"node_modules/fdir": {
2453
+
"version": "6.5.0",
2454
"dev": true,
2455
+
"license": "MIT",
2456
+
"engines": {
2457
+
"node": ">=12.0.0"
2458
+
},
2459
+
"peerDependencies": {
2460
+
"picomatch": "^3 || ^4"
2461
+
},
2462
+
"peerDependenciesMeta": {
2463
+
"picomatch": {
2464
+
"optional": true
2465
+
}
2466
}
2467
},
2468
"node_modules/file-entry-cache": {
2469
"version": "8.0.0",
2470
"dev": true,
2471
"license": "MIT",
2472
"dependencies": {
···
2476
"node": ">=16.0.0"
2477
}
2478
},
2479
"node_modules/find-cache-dir": {
2480
"version": "3.3.2",
2481
"dev": true,
2482
"license": "MIT",
2483
"dependencies": {
···
2494
},
2495
"node_modules/find-up": {
2496
"version": "5.0.0",
2497
"dev": true,
2498
"license": "MIT",
2499
"dependencies": {
···
2509
},
2510
"node_modules/flat-cache": {
2511
"version": "4.0.1",
2512
"dev": true,
2513
"license": "MIT",
2514
"dependencies": {
···
2521
},
2522
"node_modules/flatted": {
2523
"version": "3.3.3",
2524
"dev": true,
2525
"license": "ISC"
2526
},
2527
"node_modules/fs-extra": {
2528
+
"version": "10.1.0",
2529
"dev": true,
2530
"license": "MIT",
2531
"dependencies": {
···
2534
"universalify": "^2.0.0"
2535
},
2536
"engines": {
2537
+
"node": ">=12"
2538
}
2539
},
2540
"node_modules/fsevents": {
···
2554
},
2555
"node_modules/function-bind": {
2556
"version": "1.1.2",
2557
"dev": true,
2558
"license": "MIT",
2559
"funding": {
···
2562
},
2563
"node_modules/gensync": {
2564
"version": "1.0.0-beta.2",
2565
"dev": true,
2566
"license": "MIT",
2567
"engines": {
···
2570
},
2571
"node_modules/glob-parent": {
2572
"version": "6.0.2",
2573
"dev": true,
2574
"license": "ISC",
2575
"dependencies": {
···
2580
}
2581
},
2582
"node_modules/globals": {
2583
+
"version": "16.5.0",
2584
"dev": true,
2585
"license": "MIT",
2586
"engines": {
···
2592
},
2593
"node_modules/graceful-fs": {
2594
"version": "4.2.11",
2595
"dev": true,
2596
"license": "ISC"
2597
},
2598
"node_modules/graphemer": {
2599
"version": "1.4.0",
2600
"dev": true,
2601
"license": "MIT"
2602
},
2603
"node_modules/has-flag": {
2604
"version": "4.0.0",
2605
"dev": true,
2606
"license": "MIT",
2607
"engines": {
···
2610
},
2611
"node_modules/hasown": {
2612
"version": "2.0.2",
2613
"dev": true,
2614
"license": "MIT",
2615
"dependencies": {
···
2621
},
2622
"node_modules/ignore": {
2623
"version": "5.3.2",
2624
"dev": true,
2625
"license": "MIT",
2626
"engines": {
···
2629
},
2630
"node_modules/import-fresh": {
2631
"version": "3.3.1",
2632
"dev": true,
2633
"license": "MIT",
2634
"dependencies": {
···
2644
},
2645
"node_modules/import-lazy": {
2646
"version": "4.0.0",
2647
"dev": true,
2648
"license": "MIT",
2649
"engines": {
···
2652
},
2653
"node_modules/imurmurhash": {
2654
"version": "0.1.4",
2655
"dev": true,
2656
"license": "MIT",
2657
"engines": {
···
2660
},
2661
"node_modules/is-core-module": {
2662
"version": "2.16.1",
2663
"dev": true,
2664
"license": "MIT",
2665
"dependencies": {
···
2674
},
2675
"node_modules/is-extglob": {
2676
"version": "2.1.1",
2677
"dev": true,
2678
"license": "MIT",
2679
"engines": {
···
2682
},
2683
"node_modules/is-glob": {
2684
"version": "4.0.3",
2685
"dev": true,
2686
"license": "MIT",
2687
"dependencies": {
···
2691
"node": ">=0.10.0"
2692
}
2693
},
2694
"node_modules/isexe": {
2695
"version": "2.0.0",
2696
"dev": true,
2697
"license": "ISC"
2698
},
2699
"node_modules/jju": {
2700
"version": "1.4.0",
2701
"dev": true,
2702
"license": "MIT"
2703
},
2704
"node_modules/js-tokens": {
2705
"version": "4.0.0",
2706
"dev": true,
2707
"license": "MIT"
2708
},
2709
"node_modules/js-yaml": {
2710
+
"version": "4.1.1",
2711
"dev": true,
2712
"license": "MIT",
2713
"dependencies": {
···
2717
"js-yaml": "bin/js-yaml.js"
2718
}
2719
},
2720
+
"node_modules/js-yaml/node_modules/argparse": {
2721
+
"version": "2.0.1",
2722
+
"dev": true,
2723
+
"license": "Python-2.0"
2724
+
},
2725
"node_modules/jsesc": {
2726
"version": "3.1.0",
2727
"dev": true,
2728
"license": "MIT",
2729
"bin": {
···
2735
},
2736
"node_modules/json-buffer": {
2737
"version": "3.0.1",
2738
"dev": true,
2739
"license": "MIT"
2740
},
2741
"node_modules/json-schema-traverse": {
2742
+
"version": "1.0.0",
2743
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
2744
+
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
2745
"dev": true,
2746
"license": "MIT"
2747
},
2748
"node_modules/json-stable-stringify-without-jsonify": {
2749
"version": "1.0.1",
2750
"dev": true,
2751
"license": "MIT"
2752
},
2753
"node_modules/json5": {
2754
"version": "2.2.3",
2755
"dev": true,
2756
"license": "MIT",
2757
"bin": {
···
2763
},
2764
"node_modules/jsonfile": {
2765
"version": "6.2.0",
2766
"dev": true,
2767
"license": "MIT",
2768
"dependencies": {
···
2774
},
2775
"node_modules/keyv": {
2776
"version": "4.5.4",
2777
"dev": true,
2778
"license": "MIT",
2779
"dependencies": {
···
2782
},
2783
"node_modules/kolorist": {
2784
"version": "1.8.0",
2785
"dev": true,
2786
"license": "MIT"
2787
},
2788
"node_modules/levn": {
2789
"version": "0.4.1",
2790
"dev": true,
2791
"license": "MIT",
2792
"dependencies": {
···
2799
},
2800
"node_modules/lightningcss": {
2801
"version": "1.30.2",
2802
"dev": true,
2803
"license": "MPL-2.0",
2804
"dependencies": {
···
2825
"lightningcss-win32-x64-msvc": "1.30.2"
2826
}
2827
},
2828
+
"node_modules/lightningcss-android-arm64": {
2829
+
"version": "1.30.2",
2830
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
2831
+
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
2832
+
"cpu": [
2833
+
"arm64"
2834
+
],
2835
+
"dev": true,
2836
+
"license": "MPL-2.0",
2837
+
"optional": true,
2838
+
"os": [
2839
+
"android"
2840
+
],
2841
+
"engines": {
2842
+
"node": ">= 12.0.0"
2843
+
},
2844
+
"funding": {
2845
+
"type": "opencollective",
2846
+
"url": "https://opencollective.com/parcel"
2847
+
}
2848
+
},
2849
"node_modules/lightningcss-darwin-arm64": {
2850
"version": "1.30.2",
2851
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
···
2867
"url": "https://opencollective.com/parcel"
2868
}
2869
},
2870
+
"node_modules/lightningcss-darwin-x64": {
2871
+
"version": "1.30.2",
2872
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
2873
+
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
2874
+
"cpu": [
2875
+
"x64"
2876
+
],
2877
+
"dev": true,
2878
+
"license": "MPL-2.0",
2879
+
"optional": true,
2880
+
"os": [
2881
+
"darwin"
2882
+
],
2883
+
"engines": {
2884
+
"node": ">= 12.0.0"
2885
+
},
2886
+
"funding": {
2887
+
"type": "opencollective",
2888
+
"url": "https://opencollective.com/parcel"
2889
+
}
2890
+
},
2891
+
"node_modules/lightningcss-freebsd-x64": {
2892
+
"version": "1.30.2",
2893
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
2894
+
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
2895
+
"cpu": [
2896
+
"x64"
2897
+
],
2898
+
"dev": true,
2899
+
"license": "MPL-2.0",
2900
+
"optional": true,
2901
+
"os": [
2902
+
"freebsd"
2903
+
],
2904
+
"engines": {
2905
+
"node": ">= 12.0.0"
2906
+
},
2907
+
"funding": {
2908
+
"type": "opencollective",
2909
+
"url": "https://opencollective.com/parcel"
2910
+
}
2911
+
},
2912
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
2913
+
"version": "1.30.2",
2914
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
2915
+
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
2916
+
"cpu": [
2917
+
"arm"
2918
+
],
2919
+
"dev": true,
2920
+
"license": "MPL-2.0",
2921
+
"optional": true,
2922
+
"os": [
2923
+
"linux"
2924
+
],
2925
+
"engines": {
2926
+
"node": ">= 12.0.0"
2927
+
},
2928
+
"funding": {
2929
+
"type": "opencollective",
2930
+
"url": "https://opencollective.com/parcel"
2931
+
}
2932
+
},
2933
+
"node_modules/lightningcss-linux-arm64-gnu": {
2934
+
"version": "1.30.2",
2935
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
2936
+
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
2937
+
"cpu": [
2938
+
"arm64"
2939
+
],
2940
+
"dev": true,
2941
+
"license": "MPL-2.0",
2942
+
"optional": true,
2943
+
"os": [
2944
+
"linux"
2945
+
],
2946
+
"engines": {
2947
+
"node": ">= 12.0.0"
2948
+
},
2949
+
"funding": {
2950
+
"type": "opencollective",
2951
+
"url": "https://opencollective.com/parcel"
2952
+
}
2953
+
},
2954
+
"node_modules/lightningcss-linux-arm64-musl": {
2955
+
"version": "1.30.2",
2956
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
2957
+
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
2958
+
"cpu": [
2959
+
"arm64"
2960
+
],
2961
+
"dev": true,
2962
+
"license": "MPL-2.0",
2963
+
"optional": true,
2964
+
"os": [
2965
+
"linux"
2966
+
],
2967
+
"engines": {
2968
+
"node": ">= 12.0.0"
2969
+
},
2970
+
"funding": {
2971
+
"type": "opencollective",
2972
+
"url": "https://opencollective.com/parcel"
2973
+
}
2974
+
},
2975
+
"node_modules/lightningcss-linux-x64-gnu": {
2976
+
"version": "1.30.2",
2977
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
2978
+
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
2979
+
"cpu": [
2980
+
"x64"
2981
+
],
2982
+
"dev": true,
2983
+
"license": "MPL-2.0",
2984
+
"optional": true,
2985
+
"os": [
2986
+
"linux"
2987
+
],
2988
+
"engines": {
2989
+
"node": ">= 12.0.0"
2990
+
},
2991
+
"funding": {
2992
+
"type": "opencollective",
2993
+
"url": "https://opencollective.com/parcel"
2994
+
}
2995
+
},
2996
+
"node_modules/lightningcss-linux-x64-musl": {
2997
+
"version": "1.30.2",
2998
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
2999
+
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
3000
+
"cpu": [
3001
+
"x64"
3002
+
],
3003
+
"dev": true,
3004
+
"license": "MPL-2.0",
3005
+
"optional": true,
3006
+
"os": [
3007
+
"linux"
3008
+
],
3009
+
"engines": {
3010
+
"node": ">= 12.0.0"
3011
+
},
3012
+
"funding": {
3013
+
"type": "opencollective",
3014
+
"url": "https://opencollective.com/parcel"
3015
+
}
3016
+
},
3017
+
"node_modules/lightningcss-win32-arm64-msvc": {
3018
+
"version": "1.30.2",
3019
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
3020
+
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
3021
+
"cpu": [
3022
+
"arm64"
3023
+
],
3024
+
"dev": true,
3025
+
"license": "MPL-2.0",
3026
+
"optional": true,
3027
+
"os": [
3028
+
"win32"
3029
+
],
3030
+
"engines": {
3031
+
"node": ">= 12.0.0"
3032
+
},
3033
+
"funding": {
3034
+
"type": "opencollective",
3035
+
"url": "https://opencollective.com/parcel"
3036
+
}
3037
+
},
3038
+
"node_modules/lightningcss-win32-x64-msvc": {
3039
+
"version": "1.30.2",
3040
+
"cpu": [
3041
+
"x64"
3042
+
],
3043
+
"dev": true,
3044
+
"license": "MPL-2.0",
3045
+
"optional": true,
3046
+
"os": [
3047
+
"win32"
3048
+
],
3049
+
"engines": {
3050
+
"node": ">= 12.0.0"
3051
+
},
3052
+
"funding": {
3053
+
"type": "opencollective",
3054
+
"url": "https://opencollective.com/parcel"
3055
+
}
3056
+
},
3057
"node_modules/local-pkg": {
3058
"version": "1.1.2",
3059
"dev": true,
3060
"license": "MIT",
3061
"dependencies": {
···
3072
},
3073
"node_modules/locate-path": {
3074
"version": "6.0.0",
3075
"dev": true,
3076
"license": "MIT",
3077
"dependencies": {
···
3086
},
3087
"node_modules/lodash": {
3088
"version": "4.17.21",
3089
"dev": true,
3090
"license": "MIT"
3091
},
3092
"node_modules/lodash.merge": {
3093
"version": "4.6.2",
3094
"dev": true,
3095
"license": "MIT"
3096
},
3097
"node_modules/lru-cache": {
3098
+
"version": "6.0.0",
3099
"dev": true,
3100
"license": "ISC",
3101
"dependencies": {
3102
+
"yallist": "^4.0.0"
3103
+
},
3104
+
"engines": {
3105
+
"node": ">=10"
3106
}
3107
},
3108
"node_modules/magic-string": {
3109
+
"version": "0.30.21",
3110
"dev": true,
3111
"license": "MIT",
3112
"dependencies": {
···
3115
},
3116
"node_modules/make-dir": {
3117
"version": "3.1.0",
3118
"dev": true,
3119
"license": "MIT",
3120
"dependencies": {
···
3127
"url": "https://github.com/sponsors/sindresorhus"
3128
}
3129
},
3130
+
"node_modules/make-dir/node_modules/semver": {
3131
+
"version": "6.3.1",
3132
"dev": true,
3133
+
"license": "ISC",
3134
+
"bin": {
3135
+
"semver": "bin/semver.js"
3136
}
3137
},
3138
"node_modules/minimatch": {
3139
+
"version": "10.0.3",
3140
"dev": true,
3141
"license": "ISC",
3142
"dependencies": {
3143
+
"@isaacs/brace-expansion": "^5.0.0"
3144
},
3145
"engines": {
3146
+
"node": "20 || >=22"
3147
+
},
3148
+
"funding": {
3149
+
"url": "https://github.com/sponsors/isaacs"
3150
}
3151
},
3152
"node_modules/mlly": {
3153
"version": "1.8.0",
3154
"dev": true,
3155
"license": "MIT",
3156
"dependencies": {
···
3160
"ufo": "^1.6.1"
3161
}
3162
},
3163
"node_modules/mlly/node_modules/pkg-types": {
3164
"version": "1.3.1",
3165
"dev": true,
3166
"license": "MIT",
3167
"dependencies": {
···
3169
"mlly": "^1.7.4",
3170
"pathe": "^2.0.1"
3171
}
3172
+
},
3173
+
"node_modules/mlly/node_modules/pkg-types/node_modules/confbox": {
3174
+
"version": "0.1.8",
3175
+
"dev": true,
3176
+
"license": "MIT"
3177
},
3178
"node_modules/ms": {
3179
"version": "2.1.3",
3180
"dev": true,
3181
"license": "MIT"
3182
},
3183
"node_modules/nanoid": {
3184
"version": "3.3.11",
3185
"dev": true,
3186
"funding": [
3187
{
···
3199
},
3200
"node_modules/natural-compare": {
3201
"version": "1.4.0",
3202
"dev": true,
3203
"license": "MIT"
3204
},
3205
"node_modules/node-releases": {
3206
+
"version": "2.0.27",
3207
"dev": true,
3208
"license": "MIT"
3209
},
3210
"node_modules/optionator": {
3211
"version": "0.9.4",
3212
"dev": true,
3213
"license": "MIT",
3214
"dependencies": {
···
3225
},
3226
"node_modules/p-limit": {
3227
"version": "3.1.0",
3228
"dev": true,
3229
"license": "MIT",
3230
"dependencies": {
···
3239
},
3240
"node_modules/p-locate": {
3241
"version": "5.0.0",
3242
"dev": true,
3243
"license": "MIT",
3244
"dependencies": {
···
3253
},
3254
"node_modules/p-try": {
3255
"version": "2.2.0",
3256
"dev": true,
3257
"license": "MIT",
3258
"engines": {
···
3261
},
3262
"node_modules/parent-module": {
3263
"version": "1.0.1",
3264
"dev": true,
3265
"license": "MIT",
3266
"dependencies": {
···
3272
},
3273
"node_modules/path-browserify": {
3274
"version": "1.0.1",
3275
"dev": true,
3276
"license": "MIT"
3277
},
3278
"node_modules/path-exists": {
3279
"version": "4.0.0",
3280
"dev": true,
3281
"license": "MIT",
3282
"engines": {
···
3285
},
3286
"node_modules/path-key": {
3287
"version": "3.1.1",
3288
"dev": true,
3289
"license": "MIT",
3290
"engines": {
···
3293
},
3294
"node_modules/path-parse": {
3295
"version": "1.0.7",
3296
"dev": true,
3297
"license": "MIT"
3298
},
3299
"node_modules/pathe": {
3300
"version": "2.0.3",
3301
"dev": true,
3302
"license": "MIT"
3303
},
3304
"node_modules/picocolors": {
3305
"version": "1.1.1",
3306
"dev": true,
3307
"license": "ISC"
3308
},
3309
"node_modules/picomatch": {
3310
+
"version": "4.0.3",
3311
"dev": true,
3312
"license": "MIT",
3313
"engines": {
3314
+
"node": ">=12"
3315
},
3316
"funding": {
3317
"url": "https://github.com/sponsors/jonschlinkert"
···
3319
},
3320
"node_modules/pkg-dir": {
3321
"version": "4.2.0",
3322
"dev": true,
3323
"license": "MIT",
3324
"dependencies": {
···
3330
},
3331
"node_modules/pkg-dir/node_modules/find-up": {
3332
"version": "4.1.0",
3333
"dev": true,
3334
"license": "MIT",
3335
"dependencies": {
···
3340
"node": ">=8"
3341
}
3342
},
3343
+
"node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path": {
3344
"version": "5.0.0",
3345
"dev": true,
3346
"license": "MIT",
3347
"dependencies": {
···
3351
"node": ">=8"
3352
}
3353
},
3354
+
"node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": {
3355
+
"version": "4.1.0",
3356
"dev": true,
3357
"license": "MIT",
3358
"dependencies": {
3359
+
"p-limit": "^2.2.0"
3360
},
3361
"engines": {
3362
+
"node": ">=8"
3363
}
3364
},
3365
+
"node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": {
3366
+
"version": "2.3.0",
3367
"dev": true,
3368
"license": "MIT",
3369
"dependencies": {
3370
+
"p-try": "^2.0.0"
3371
},
3372
"engines": {
3373
+
"node": ">=6"
3374
+
},
3375
+
"funding": {
3376
+
"url": "https://github.com/sponsors/sindresorhus"
3377
}
3378
},
3379
"node_modules/pkg-types": {
3380
"version": "2.3.0",
3381
"dev": true,
3382
"license": "MIT",
3383
"dependencies": {
···
3388
},
3389
"node_modules/postcss": {
3390
"version": "8.5.6",
3391
"dev": true,
3392
"funding": [
3393
{
···
3415
},
3416
"node_modules/prelude-ls": {
3417
"version": "1.2.1",
3418
"dev": true,
3419
"license": "MIT",
3420
"engines": {
···
3423
},
3424
"node_modules/punycode": {
3425
"version": "2.3.1",
3426
"dev": true,
3427
"license": "MIT",
3428
"engines": {
···
3431
},
3432
"node_modules/quansync": {
3433
"version": "0.2.11",
3434
"dev": true,
3435
"funding": [
3436
{
···
3444
],
3445
"license": "MIT"
3446
},
3447
"node_modules/react": {
3448
"version": "19.2.0",
3449
"dev": true,
3450
"license": "MIT",
3451
+
"peer": true,
3452
"engines": {
3453
"node": ">=0.10.0"
3454
}
3455
},
3456
"node_modules/react-dom": {
3457
"version": "19.2.0",
3458
"dev": true,
3459
"license": "MIT",
3460
"dependencies": {
···
3465
}
3466
},
3467
"node_modules/react-refresh": {
3468
+
"version": "0.18.0",
3469
"dev": true,
3470
"license": "MIT",
3471
"engines": {
···
3474
},
3475
"node_modules/require-from-string": {
3476
"version": "2.0.2",
3477
"dev": true,
3478
"license": "MIT",
3479
"engines": {
···
3481
}
3482
},
3483
"node_modules/resolve": {
3484
+
"version": "1.22.11",
3485
"dev": true,
3486
"license": "MIT",
3487
"dependencies": {
3488
+
"is-core-module": "^2.16.1",
3489
"path-parse": "^1.0.7",
3490
"supports-preserve-symlinks-flag": "^1.0.0"
3491
},
···
3501
},
3502
"node_modules/resolve-from": {
3503
"version": "4.0.0",
3504
"dev": true,
3505
"license": "MIT",
3506
"engines": {
3507
"node": ">=4"
3508
}
3509
},
3510
"node_modules/rolldown": {
3511
"version": "1.0.0-beta.41",
3512
"dev": true,
3513
"license": "MIT",
3514
+
"peer": true,
3515
"dependencies": {
3516
"@oxc-project/types": "=0.93.0",
3517
"@rolldown/pluginutils": "1.0.0-beta.41",
···
3542
},
3543
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
3544
"version": "1.0.0-beta.41",
3545
"dev": true,
3546
"license": "MIT"
3547
},
3548
"node_modules/rollup": {
3549
+
"version": "4.53.3",
3550
"dev": true,
3551
"license": "MIT",
3552
"peer": true,
···
3561
"npm": ">=8.0.0"
3562
},
3563
"optionalDependencies": {
3564
+
"@rollup/rollup-android-arm-eabi": "4.53.3",
3565
+
"@rollup/rollup-android-arm64": "4.53.3",
3566
+
"@rollup/rollup-darwin-arm64": "4.53.3",
3567
+
"@rollup/rollup-darwin-x64": "4.53.3",
3568
+
"@rollup/rollup-freebsd-arm64": "4.53.3",
3569
+
"@rollup/rollup-freebsd-x64": "4.53.3",
3570
+
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
3571
+
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
3572
+
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
3573
+
"@rollup/rollup-linux-arm64-musl": "4.53.3",
3574
+
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
3575
+
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
3576
+
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
3577
+
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
3578
+
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
3579
+
"@rollup/rollup-linux-x64-gnu": "4.53.3",
3580
+
"@rollup/rollup-linux-x64-musl": "4.53.3",
3581
+
"@rollup/rollup-openharmony-arm64": "4.53.3",
3582
+
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
3583
+
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
3584
+
"@rollup/rollup-win32-x64-gnu": "4.53.3",
3585
+
"@rollup/rollup-win32-x64-msvc": "4.53.3",
3586
"fsevents": "~2.3.2"
3587
}
3588
},
3589
"node_modules/rollup-plugin-typescript2": {
3590
"version": "0.36.0",
3591
"dev": true,
3592
"license": "MIT",
3593
"dependencies": {
···
3602
"typescript": ">=2.4.0"
3603
}
3604
},
3605
"node_modules/rollup-plugin-typescript2/node_modules/semver": {
3606
"version": "7.7.3",
3607
"dev": true,
3608
"license": "ISC",
3609
"bin": {
···
3613
"node": ">=10"
3614
}
3615
},
3616
"node_modules/scheduler": {
3617
"version": "0.27.0",
3618
"dev": true,
3619
"license": "MIT"
3620
},
3621
"node_modules/semver": {
3622
+
"version": "7.5.4",
3623
"dev": true,
3624
"license": "ISC",
3625
+
"dependencies": {
3626
+
"lru-cache": "^6.0.0"
3627
+
},
3628
"bin": {
3629
"semver": "bin/semver.js"
3630
+
},
3631
+
"engines": {
3632
+
"node": ">=10"
3633
}
3634
},
3635
"node_modules/shebang-command": {
3636
"version": "2.0.0",
3637
"dev": true,
3638
"license": "MIT",
3639
"dependencies": {
···
3645
},
3646
"node_modules/shebang-regex": {
3647
"version": "3.0.0",
3648
"dev": true,
3649
"license": "MIT",
3650
"engines": {
···
3653
},
3654
"node_modules/source-map": {
3655
"version": "0.6.1",
3656
"dev": true,
3657
"license": "BSD-3-Clause",
3658
"engines": {
···
3661
},
3662
"node_modules/source-map-js": {
3663
"version": "1.2.1",
3664
"dev": true,
3665
"license": "BSD-3-Clause",
3666
"engines": {
···
3669
},
3670
"node_modules/source-map-support": {
3671
"version": "0.5.21",
3672
"dev": true,
3673
"license": "MIT",
3674
"optional": true,
3675
"dependencies": {
3676
"buffer-from": "^1.0.0",
3677
"source-map": "^0.6.0"
···
3679
},
3680
"node_modules/sprintf-js": {
3681
"version": "1.0.3",
3682
"dev": true,
3683
"license": "BSD-3-Clause"
3684
},
3685
"node_modules/string-argv": {
3686
"version": "0.3.2",
3687
"dev": true,
3688
"license": "MIT",
3689
"engines": {
···
3692
},
3693
"node_modules/strip-json-comments": {
3694
"version": "3.1.1",
3695
"dev": true,
3696
"license": "MIT",
3697
"engines": {
···
3702
}
3703
},
3704
"node_modules/supports-color": {
3705
+
"version": "8.1.1",
3706
"dev": true,
3707
"license": "MIT",
3708
"dependencies": {
3709
"has-flag": "^4.0.0"
3710
},
3711
"engines": {
3712
+
"node": ">=10"
3713
+
},
3714
+
"funding": {
3715
+
"url": "https://github.com/chalk/supports-color?sponsor=1"
3716
}
3717
},
3718
"node_modules/supports-preserve-symlinks-flag": {
3719
"version": "1.0.0",
3720
"dev": true,
3721
"license": "MIT",
3722
"engines": {
···
3726
"url": "https://github.com/sponsors/ljharb"
3727
}
3728
},
3729
"node_modules/tinyglobby": {
3730
"version": "0.2.15",
3731
"dev": true,
3732
"license": "MIT",
3733
"dependencies": {
···
3741
"url": "https://github.com/sponsors/SuperchupuDev"
3742
}
3743
},
3744
"node_modules/ts-api-utils": {
3745
"version": "2.1.0",
3746
"dev": true,
3747
"license": "MIT",
3748
"engines": {
···
3754
},
3755
"node_modules/tslib": {
3756
"version": "2.8.1",
3757
"dev": true,
3758
"license": "0BSD"
3759
},
3760
"node_modules/type-check": {
3761
"version": "0.4.0",
3762
"dev": true,
3763
"license": "MIT",
3764
"dependencies": {
···
3774
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
3775
"dev": true,
3776
"license": "Apache-2.0",
3777
+
"peer": true,
3778
"bin": {
3779
"tsc": "bin/tsc",
3780
"tsserver": "bin/tsserver"
···
3784
}
3785
},
3786
"node_modules/typescript-eslint": {
3787
+
"version": "8.48.1",
3788
"dev": true,
3789
"license": "MIT",
3790
"dependencies": {
3791
+
"@typescript-eslint/eslint-plugin": "8.48.1",
3792
+
"@typescript-eslint/parser": "8.48.1",
3793
+
"@typescript-eslint/typescript-estree": "8.48.1",
3794
+
"@typescript-eslint/utils": "8.48.1"
3795
},
3796
"engines": {
3797
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
3807
},
3808
"node_modules/ufo": {
3809
"version": "1.6.1",
3810
"dev": true,
3811
"license": "MIT"
3812
},
3813
"node_modules/undici-types": {
3814
+
"version": "7.16.0",
3815
"dev": true,
3816
"license": "MIT"
3817
},
3818
"node_modules/universalify": {
3819
"version": "2.0.1",
3820
"dev": true,
3821
"license": "MIT",
3822
"engines": {
···
3824
}
3825
},
3826
"node_modules/unplugin": {
3827
+
"version": "2.3.11",
3828
"dev": true,
3829
"license": "MIT",
3830
"dependencies": {
···
3839
},
3840
"node_modules/unplugin-dts": {
3841
"version": "1.0.0-beta.6",
3842
"dev": true,
3843
"license": "MIT",
3844
"dependencies": {
···
3889
}
3890
}
3891
},
3892
+
"node_modules/unplugin-dts/node_modules/@rollup/pluginutils": {
3893
+
"version": "5.3.0",
3894
"dev": true,
3895
"license": "MIT",
3896
+
"dependencies": {
3897
+
"@types/estree": "^1.0.0",
3898
+
"estree-walker": "^2.0.2",
3899
+
"picomatch": "^4.0.2"
3900
+
},
3901
"engines": {
3902
+
"node": ">=14.0.0"
3903
+
},
3904
+
"peerDependencies": {
3905
+
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
3906
},
3907
+
"peerDependenciesMeta": {
3908
+
"rollup": {
3909
+
"optional": true
3910
+
}
3911
}
3912
},
3913
"node_modules/update-browserslist-db": {
3914
+
"version": "1.1.4",
3915
"dev": true,
3916
"funding": [
3917
{
···
3941
},
3942
"node_modules/uri-js": {
3943
"version": "4.4.1",
3944
"dev": true,
3945
"license": "BSD-2-Clause",
3946
"dependencies": {
···
3950
"node_modules/vite": {
3951
"name": "rolldown-vite",
3952
"version": "7.1.14",
3953
"dev": true,
3954
"license": "MIT",
3955
+
"peer": true,
3956
"dependencies": {
3957
"@oxc-project/runtime": "0.92.0",
3958
"fdir": "^6.5.0",
···
4023
}
4024
}
4025
},
4026
"node_modules/vscode-uri": {
4027
"version": "3.1.0",
4028
"dev": true,
4029
"license": "MIT"
4030
},
4031
"node_modules/webpack-virtual-modules": {
4032
"version": "0.6.2",
4033
"dev": true,
4034
"license": "MIT"
4035
},
4036
"node_modules/which": {
4037
"version": "2.0.2",
4038
"dev": true,
4039
"license": "ISC",
4040
"dependencies": {
···
4049
},
4050
"node_modules/word-wrap": {
4051
"version": "1.2.5",
4052
"dev": true,
4053
"license": "MIT",
4054
"engines": {
···
4056
}
4057
},
4058
"node_modules/yallist": {
4059
+
"version": "4.0.0",
4060
"dev": true,
4061
"license": "ISC"
4062
},
4063
"node_modules/yocto-queue": {
4064
"version": "0.1.0",
4065
"dev": true,
4066
"license": "MIT",
4067
"engines": {
+3
-4
package.json
+3
-4
package.json
···
1
{
2
"name": "atproto-ui",
3
-
"version": "0.5.5",
4
"type": "module",
5
"description": "React components and hooks for rendering AT Protocol records.",
6
"main": "./lib-dist/index.js",
···
18
"README.md"
19
],
20
"sideEffects": [
21
-
"./lib-dist/styles.css",
22
-
"./lib-dist/index.js"
23
],
24
"scripts": {
25
"dev": "vite",
···
44
"@atcute/bluesky": "^3.2.3",
45
"@atcute/client": "^4.0.3",
46
"@atcute/identity-resolver": "^1.1.3",
47
-
"@atcute/tangled": "^1.0.6"
48
},
49
"devDependencies": {
50
"@eslint/js": "^9.36.0",
···
1
{
2
"name": "atproto-ui",
3
+
"version": "0.12.0",
4
"type": "module",
5
"description": "React components and hooks for rendering AT Protocol records.",
6
"main": "./lib-dist/index.js",
···
18
"README.md"
19
],
20
"sideEffects": [
21
+
"./lib-dist/styles.css"
22
],
23
"scripts": {
24
"dev": "vite",
···
43
"@atcute/bluesky": "^3.2.3",
44
"@atcute/client": "^4.0.3",
45
"@atcute/identity-resolver": "^1.1.3",
46
+
"@atcute/tangled": "^1.0.10"
47
},
48
"devDependencies": {
49
"@eslint/js": "^9.36.0",
+118
-2
src/App.tsx
+118
-2
src/App.tsx
···
1
import React, { useState, useCallback, useRef } from "react";
2
-
import { AtProtoProvider } from "../lib";
3
-
import "../lib/styles.css"
4
import "./App.css";
5
6
import { TangledString } from "../lib/components/TangledString";
···
12
} from "../lib/components/BlueskyPost";
13
import { BlueskyPostList } from "../lib/components/BlueskyPostList";
14
import { BlueskyQuotePost } from "../lib/components/BlueskyQuotePost";
15
import { useDidResolution } from "../lib/hooks/useDidResolution";
16
import { useLatestRecord } from "../lib/hooks/useLatestRecord";
17
import type { FeedPostRecord } from "../lib/types/bluesky";
···
42
// Pass prefetched recordโBlueskyPost won't re-fetch it
43
return <BlueskyPost did={did} rkey={rkey} record={record} />;
44
};`;
45
46
const codeBlockBase: React.CSSProperties = {
47
fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace',
···
255
<h3 style={sectionHeaderStyle}>Recent Posts</h3>
256
<BlueskyPostList did={did} />
257
</section>
258
</div>
259
<div style={columnStackStyle}>
260
<section style={panelStyle}>
···
355
/>
356
</section>
357
<section style={panelStyle}>
358
<h3 style={sectionHeaderStyle}>
359
Custom Themed Post
360
</h3>
···
460
style={codeTextStyle}
461
>
462
{prefetchedDataSnippet}
463
</code>
464
</pre>
465
</section>
···
1
import React, { useState, useCallback, useRef } from "react";
2
+
import { AtProtoProvider, TangledRepo } from "../lib";
3
+
import "../lib/styles.css";
4
import "./App.css";
5
6
import { TangledString } from "../lib/components/TangledString";
···
12
} from "../lib/components/BlueskyPost";
13
import { BlueskyPostList } from "../lib/components/BlueskyPostList";
14
import { BlueskyQuotePost } from "../lib/components/BlueskyQuotePost";
15
+
import { GrainGallery } from "../lib/components/GrainGallery";
16
+
import { CurrentlyPlaying } from "../lib/components/CurrentlyPlaying";
17
+
import { LastPlayed } from "../lib/components/LastPlayed";
18
+
import { SongHistoryList } from "../lib/components/SongHistoryList";
19
import { useDidResolution } from "../lib/hooks/useDidResolution";
20
import { useLatestRecord } from "../lib/hooks/useLatestRecord";
21
import type { FeedPostRecord } from "../lib/types/bluesky";
···
46
// Pass prefetched recordโBlueskyPost won't re-fetch it
47
return <BlueskyPost did={did} rkey={rkey} record={record} />;
48
};`;
49
+
50
+
const atcuteUsageSnippet = `import { Client, simpleFetchHandler, ok } from '@atcute/client';
51
+
import type { AppBskyFeedPost } from '@atcute/bluesky';
52
+
import { BlueskyPost } from 'atproto-ui';
53
+
54
+
// Create atcute client
55
+
const client = new Client({
56
+
handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' })
57
+
});
58
+
59
+
// Fetch a record
60
+
const data = await ok(
61
+
client.get('com.atproto.repo.getRecord', {
62
+
params: {
63
+
repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq',
64
+
collection: 'app.bsky.feed.post',
65
+
rkey: '3m45rq4sjes2h'
66
+
}
67
+
})
68
+
);
69
+
70
+
const record = data.value as AppBskyFeedPost.Main;
71
+
72
+
// Pass atcute record directly to component!
73
+
<BlueskyPost
74
+
did="did:plc:ttdrpj45ibqunmfhdsb4zdwq"
75
+
rkey="3m45rq4sjes2h"
76
+
record={record}
77
+
/>`;
78
79
const codeBlockBase: React.CSSProperties = {
80
fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace',
···
288
<h3 style={sectionHeaderStyle}>Recent Posts</h3>
289
<BlueskyPostList did={did} />
290
</section>
291
+
<section style={panelStyle}>
292
+
<h3 style={sectionHeaderStyle}>
293
+
grain.social Gallery Demo
294
+
</h3>
295
+
<p
296
+
style={{
297
+
fontSize: 12,
298
+
color: `var(--demo-text-secondary)`,
299
+
margin: "0 0 8px",
300
+
}}
301
+
>
302
+
Instagram-style photo gallery from grain.social
303
+
</p>
304
+
<GrainGallery
305
+
did="kat.meangirls.online"
306
+
rkey="3m2e2qikseq2f"
307
+
/>
308
+
</section>
309
+
<section style={panelStyle}>
310
+
<h3 style={sectionHeaderStyle}>
311
+
teal.fm Currently Playing
312
+
</h3>
313
+
<p
314
+
style={{
315
+
fontSize: 12,
316
+
color: `var(--demo-text-secondary)`,
317
+
margin: "0 0 8px",
318
+
}}
319
+
>
320
+
Currently playing track from teal.fm (refreshes every 15s)
321
+
</p>
322
+
<CurrentlyPlaying did="nekomimi.pet" />
323
+
</section>
324
+
<section style={panelStyle}>
325
+
<h3 style={sectionHeaderStyle}>
326
+
teal.fm Last Played
327
+
</h3>
328
+
<p
329
+
style={{
330
+
fontSize: 12,
331
+
color: `var(--demo-text-secondary)`,
332
+
margin: "0 0 8px",
333
+
}}
334
+
>
335
+
Most recent play from teal.fm feed
336
+
</p>
337
+
<LastPlayed did="nekomimi.pet" />
338
+
</section>
339
+
<section style={panelStyle}>
340
+
<h3 style={sectionHeaderStyle}>
341
+
teal.fm Song History
342
+
</h3>
343
+
<p
344
+
style={{
345
+
fontSize: 12,
346
+
color: `var(--demo-text-secondary)`,
347
+
margin: "0 0 8px",
348
+
}}
349
+
>
350
+
Listening history with album art focus
351
+
</p>
352
+
<SongHistoryList did="nekomimi.pet" limit={6} />
353
+
</section>
354
</div>
355
<div style={columnStackStyle}>
356
<section style={panelStyle}>
···
451
/>
452
</section>
453
<section style={panelStyle}>
454
+
<TangledRepo
455
+
did="did:plc:ttdrpj45ibqunmfhdsb4zdwq"
456
+
rkey="3m2sx5zpxzs22"
457
+
/>
458
+
</section>
459
+
<section style={panelStyle}>
460
<h3 style={sectionHeaderStyle}>
461
Custom Themed Post
462
</h3>
···
562
style={codeTextStyle}
563
>
564
{prefetchedDataSnippet}
565
+
</code>
566
+
</pre>
567
+
<p
568
+
style={{
569
+
color: `var(--demo-text-secondary)`,
570
+
margin: "16px 0 8px",
571
+
}}
572
+
>
573
+
Use atcute directly to construct records and pass them to
574
+
componentsโfully compatible!
575
+
</p>
576
+
<pre style={codeBlockStyle}>
577
+
<code className="language-tsx" style={codeTextStyle}>
578
+
{atcuteUsageSnippet}
579
</code>
580
</pre>
581
</section>
+2
-1
tsconfig.lib.json
+2
-1
tsconfig.lib.json
+2
-3
vite.config.ts
+2
-3
vite.config.ts
···
35
rollupOptions: {
36
input: resolve(__dirname, 'index.html')
37
},
38
-
sourcemap: true
39
} : {
40
// Library build configuration
41
lib: {
42
entry: resolve(__dirname, 'lib/index.ts'),
43
name: 'atproto-ui',
44
formats: ['es'],
45
fileName: 'atproto-ui'
···
71
}
72
}
73
},
74
-
sourcemap: true,
75
-
minify: false
76
}
77
});
···
35
rollupOptions: {
36
input: resolve(__dirname, 'index.html')
37
},
38
+
sourcemap: false
39
} : {
40
// Library build configuration
41
lib: {
42
entry: resolve(__dirname, 'lib/index.ts'),
43
+
cssFileName: resolve(__dirname, 'lib/styles.css'),
44
name: 'atproto-ui',
45
formats: ['es'],
46
fileName: 'atproto-ui'
···
72
}
73
}
74
},
75
}
76
});