From d16eaa324a95228ab13a6f9704b6a7202fde7068 Mon Sep 17 00:00:00 2001 From: afkarxyz Date: Fri, 5 Dec 2025 05:25:50 +0700 Subject: [PATCH] v6.6 --- app.go | 42 + backend/cover.go | 176 ++++ frontend/index.html | 2 +- frontend/package.json | 9 +- frontend/package.json.md5 | 2 +- frontend/pnpm-lock.yaml | 904 ++++++++---------- frontend/src/App.tsx | 348 ++++--- frontend/src/components/AlbumInfo.tsx | 39 +- frontend/src/components/ArtistInfo.tsx | 40 +- frontend/src/components/AudioAnalysisPage.tsx | 19 +- frontend/src/components/DebugLoggerPage.tsx | 112 +++ .../src/components/DownloadProgressToast.tsx | 2 +- frontend/src/components/Header.tsx | 65 +- frontend/src/components/PlaylistInfo.tsx | 39 +- frontend/src/components/Settings.tsx | 24 +- frontend/src/components/SettingsPage.tsx | 345 +++++++ frontend/src/components/Sidebar.tsx | 86 ++ frontend/src/components/TitleBar.tsx | 47 +- frontend/src/components/TrackInfo.tsx | 99 +- frontend/src/components/TrackList.tsx | 130 ++- frontend/src/components/ui/sonner.tsx | 1 + frontend/src/components/ui/switch.tsx | 31 + frontend/src/hooks/useCover.ts | 218 +++++ frontend/src/lib/api.ts | 11 +- frontend/src/lib/relative-time.ts | 59 ++ frontend/src/lib/settings.ts | 25 + frontend/src/lib/toast-with-sound.ts | 14 +- frontend/src/main.tsx | 4 - frontend/src/types/api.ts | 18 + version.json | 2 +- wails.json | 2 +- 31 files changed, 2091 insertions(+), 824 deletions(-) create mode 100644 backend/cover.go create mode 100644 frontend/src/components/DebugLoggerPage.tsx create mode 100644 frontend/src/components/SettingsPage.tsx create mode 100644 frontend/src/components/Sidebar.tsx create mode 100644 frontend/src/components/ui/switch.tsx create mode 100644 frontend/src/hooks/useCover.ts create mode 100644 frontend/src/lib/relative-time.ts diff --git a/app.go b/app.go index 7b139ce..4a38da3 100644 --- a/app.go +++ b/app.go @@ -482,6 +482,48 @@ func (a *App) DownloadLyrics(req LyricsDownloadRequest) (backend.LyricsDownloadR return *resp, nil } +// CoverDownloadRequest represents the request structure for downloading cover art +type CoverDownloadRequest struct { + CoverURL string `json:"cover_url"` + TrackName string `json:"track_name"` + ArtistName string `json:"artist_name"` + OutputDir string `json:"output_dir"` + FilenameFormat string `json:"filename_format"` + TrackNumber bool `json:"track_number"` + Position int `json:"position"` +} + +// DownloadCover downloads cover art for a single track +func (a *App) DownloadCover(req CoverDownloadRequest) (backend.CoverDownloadResponse, error) { + if req.CoverURL == "" { + return backend.CoverDownloadResponse{ + Success: false, + Error: "Cover URL is required", + }, fmt.Errorf("cover URL is required") + } + + client := backend.NewCoverClient() + backendReq := backend.CoverDownloadRequest{ + CoverURL: req.CoverURL, + TrackName: req.TrackName, + ArtistName: req.ArtistName, + OutputDir: req.OutputDir, + FilenameFormat: req.FilenameFormat, + TrackNumber: req.TrackNumber, + Position: req.Position, + } + + resp, err := client.DownloadCover(backendReq) + if err != nil { + return backend.CoverDownloadResponse{ + Success: false, + Error: err.Error(), + }, err + } + + return *resp, nil +} + // CheckTrackAvailability checks the availability of a track on different streaming platforms func (a *App) CheckTrackAvailability(spotifyTrackID string, isrc string) (string, error) { if spotifyTrackID == "" { diff --git a/backend/cover.go b/backend/cover.go new file mode 100644 index 0000000..bab960b --- /dev/null +++ b/backend/cover.go @@ -0,0 +1,176 @@ +package backend + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +const ( + // Spotify image size codes + spotifySize640 = "ab67616d0000b273" // 640x640 + spotifySizeMax = "ab67616d000082c1" // Max resolution +) + +// CoverDownloadRequest represents a request to download cover art +type CoverDownloadRequest struct { + CoverURL string `json:"cover_url"` + TrackName string `json:"track_name"` + ArtistName string `json:"artist_name"` + OutputDir string `json:"output_dir"` + FilenameFormat string `json:"filename_format"` + TrackNumber bool `json:"track_number"` + Position int `json:"position"` +} + +// CoverDownloadResponse represents the response from cover download +type CoverDownloadResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + File string `json:"file,omitempty"` + Error string `json:"error,omitempty"` + AlreadyExists bool `json:"already_exists,omitempty"` +} + +// CoverClient handles cover art downloading +type CoverClient struct { + httpClient *http.Client +} + +// NewCoverClient creates a new cover client +func NewCoverClient() *CoverClient { + return &CoverClient{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + } +} + +// buildCoverFilename builds the cover filename based on settings (same as track filename) +func buildCoverFilename(trackName, artistName, filenameFormat string, includeTrackNumber bool, position int) string { + safeTitle := sanitizeFilename(trackName) + safeArtist := sanitizeFilename(artistName) + + var filename string + + // Build base filename based on format + switch filenameFormat { + case "artist-title": + filename = fmt.Sprintf("%s - %s", safeArtist, safeTitle) + case "title": + filename = safeTitle + default: // "title-artist" + filename = fmt.Sprintf("%s - %s", safeTitle, safeArtist) + } + + // Add track number prefix if enabled + if includeTrackNumber && position > 0 { + filename = fmt.Sprintf("%02d. %s", position, filename) + } + + return filename + ".jpg" +} + +// getMaxResolutionURL converts a Spotify cover URL to max resolution +// Falls back to original URL if max resolution is not available +func (c *CoverClient) getMaxResolutionURL(coverURL string) string { + // Try to convert to max resolution + if strings.Contains(coverURL, spotifySize640) { + maxURL := strings.Replace(coverURL, spotifySize640, spotifySizeMax, 1) + // Check if max resolution URL is available + resp, err := c.httpClient.Head(maxURL) + if err == nil && resp.StatusCode == http.StatusOK { + return maxURL + } + } + // Return original URL as fallback + return coverURL +} + +// DownloadCover downloads cover art for a single track +func (c *CoverClient) DownloadCover(req CoverDownloadRequest) (*CoverDownloadResponse, error) { + if req.CoverURL == "" { + return &CoverDownloadResponse{ + Success: false, + Error: "Cover URL is required", + }, fmt.Errorf("cover URL is required") + } + + // Create output directory if it doesn't exist + outputDir := req.OutputDir + if outputDir == "" { + outputDir = GetDefaultMusicPath() + } + + if err := os.MkdirAll(outputDir, 0755); err != nil { + return &CoverDownloadResponse{ + Success: false, + Error: fmt.Sprintf("failed to create output directory: %v", err), + }, err + } + + // Generate filename using same format as track + filenameFormat := req.FilenameFormat + if filenameFormat == "" { + filenameFormat = "title-artist" // default + } + filename := buildCoverFilename(req.TrackName, req.ArtistName, filenameFormat, req.TrackNumber, req.Position) + filePath := filepath.Join(outputDir, filename) + + // Check if file already exists + if fileInfo, err := os.Stat(filePath); err == nil && fileInfo.Size() > 0 { + return &CoverDownloadResponse{ + Success: true, + Message: "Cover file already exists", + File: filePath, + AlreadyExists: true, + }, nil + } + + // Try to get max resolution URL, fallback to original + downloadURL := c.getMaxResolutionURL(req.CoverURL) + + // Download cover image + resp, err := c.httpClient.Get(downloadURL) + if err != nil { + return &CoverDownloadResponse{ + Success: false, + Error: fmt.Sprintf("failed to download cover: %v", err), + }, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return &CoverDownloadResponse{ + Success: false, + Error: fmt.Sprintf("failed to download cover: HTTP %d", resp.StatusCode), + }, fmt.Errorf("HTTP %d", resp.StatusCode) + } + + // Create file + file, err := os.Create(filePath) + if err != nil { + return &CoverDownloadResponse{ + Success: false, + Error: fmt.Sprintf("failed to create file: %v", err), + }, err + } + defer file.Close() + + // Write content to file + _, err = io.Copy(file, resp.Body) + if err != nil { + return &CoverDownloadResponse{ + Success: false, + Error: fmt.Sprintf("failed to write cover file: %v", err), + }, err + } + + return &CoverDownloadResponse{ + Success: true, + Message: "Cover downloaded successfully", + File: filePath, + }, nil +} diff --git a/frontend/index.html b/frontend/index.html index fc4549a..db314db 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,7 +6,7 @@ - + SpotiFLAC diff --git a/frontend/package.json b/frontend/package.json index bd29d97..d92b6ed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,12 +21,13 @@ "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.17", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.554.0", + "lucide-react": "^0.555.0", "next-themes": "^0.4.6", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -37,7 +38,7 @@ "devDependencies": { "@eslint/js": "^9.39.1", "@types/node": "^24.10.1", - "@types/react": "^19.2.6", + "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", "eslint": "^9.39.1", @@ -47,7 +48,7 @@ "sharp": "^0.34.5", "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.47.0", - "vite": "^7.2.4" + "typescript-eslint": "^8.48.0", + "vite": "^7.2.6" } } diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 0a6a418..9434da5 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -23a60910537eca7800052fa01bf45b7a \ No newline at end of file +b7a549e463d5f6a2fad25f5ce939cdd7 \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 8b18aa0..f0432a6 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,40 +10,43 @@ importers: dependencies: '@radix-ui/react-checkbox': specifier: ^1.3.3 - version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-context-menu': specifier: ^2.2.16 - version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-dialog': specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-label': specifier: ^2.1.8 - version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-progress': specifier: ^1.1.8 - version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-radio-group': specifier: ^1.3.8 - version: 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-scroll-area': specifier: ^1.2.10 - version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-select': specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.2.4(@types/react@19.2.6)(react@19.2.0) + version: 1.2.4(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-switch': + specifier: ^1.1.3 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-tabs': specifier: ^1.1.13 - version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-tooltip': specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -51,8 +54,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 lucide-react: - specifier: ^0.554.0 - version: 0.554.0(react@19.2.0) + specifier: ^0.555.0 + version: 0.555.0(react@19.2.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -79,14 +82,14 @@ importers: specifier: ^24.10.1 version: 24.10.1 '@types/react': - specifier: ^19.2.6 - version: 19.2.6 + specifier: ^19.2.7 + version: 19.2.7 '@types/react-dom': specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.6) + version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + version: 5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) eslint: specifier: ^9.39.1 version: 9.39.1(jiti@2.6.1) @@ -109,11 +112,11 @@ importers: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.47.0 - version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.48.0 + version: 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) packages: @@ -381,8 +384,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.1': @@ -581,18 +584,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -918,6 +909,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tabs@1.1.13': resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} peerDependencies: @@ -1261,66 +1265,66 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.6': - resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} - '@typescript-eslint/eslint-plugin@8.47.0': - resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.47.0 + '@typescript-eslint/parser': ^8.48.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.47.0': - resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.47.0': - resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.47.0': - resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.47.0': - resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.47.0': - resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.47.0': - resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.47.0': - resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.47.0': - resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.47.0': - resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@5.1.1': @@ -1356,8 +1360,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.30: - resolution: {integrity: sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true brace-expansion@1.1.12: @@ -1366,10 +1370,6 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - browserslist@4.28.0: resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1379,8 +1379,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001756: - resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1432,8 +1432,8 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - electron-to-chromium@1.5.259: - resolution: {integrity: sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==} + electron-to-chromium@1.5.262: + resolution: {integrity: sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==} enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} @@ -1508,19 +1508,12 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1534,10 +1527,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1562,10 +1551,6 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -1618,10 +1603,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1742,22 +1723,14 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.554.0: - resolution: {integrity: sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==} + lucide-react@0.555.0: + resolution: {integrity: sha512-D8FvHUGbxWBRQM90NZeIyhAvkFfsh3u9ekrMvJ30Z6gnpBHS6HC6ldLg7tL45hwiIz/u66eKDtdA23gwwGsAHA==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1812,10 +1785,6 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -1832,9 +1801,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.2.0: resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: @@ -1854,8 +1820,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -1882,18 +1848,11 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1950,10 +1909,6 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -1970,8 +1925,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.47.0: - resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} + typescript-eslint@8.48.0: + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2014,8 +1969,8 @@ packages: '@types/react': optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2076,8 +2031,8 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.1.12: - resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zod@4.1.13: + resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} snapshots: @@ -2299,7 +2254,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3 @@ -2465,450 +2420,453 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-context@1.1.2(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-context@1.1.3(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-context@1.1.3(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) aria-hidden: 1.2.6 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-remove-scroll: 2.7.1(@types/react@19.2.6)(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-id@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) aria-hidden: 1.2.6 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-remove-scroll: 2.7.1(@types/react@19.2.6)(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) '@radix-ui/rect': 1.1.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-context': 1.1.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) aria-hidden: 1.2.6 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-remove-scroll: 2.7.1(@types/react@19.2.6)(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-slot@1.2.4(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.6)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.6)(react@19.2.0)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 - '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) '@radix-ui/rect@1.1.1': {} @@ -3041,12 +2999,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) '@types/babel__core@7.20.5': dependencies: @@ -3077,22 +3035,22 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/react-dom@19.2.3(@types/react@19.2.6)': + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - '@types/react@19.2.6': + '@types/react@19.2.7': dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -3102,41 +3060,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.47.0': + '@typescript-eslint/scope-manager@8.48.0': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 - '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3144,41 +3102,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.47.0': {} + '@typescript-eslint/types@8.48.0': {} - '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/visitor-keys': 8.47.0 + '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.47.0': + '@typescript-eslint/visitor-keys@8.48.0': dependencies: - '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/types': 8.48.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + '@vitejs/plugin-react@5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -3186,7 +3143,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color @@ -3215,7 +3172,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.30: {} + baseline-browser-mapping@2.8.32: {} brace-expansion@1.1.12: dependencies: @@ -3226,21 +3183,17 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.30 - caniuse-lite: 1.0.30001756 - electron-to-chromium: 1.5.259 + baseline-browser-mapping: 2.8.32 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.262 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) callsites@3.1.0: {} - caniuse-lite@1.0.30001756: {} + caniuse-lite@1.0.30001757: {} chalk@4.1.2: dependencies: @@ -3281,7 +3234,7 @@ snapshots: detect-node-es@1.1.0: {} - electron-to-chromium@1.5.259: {} + electron-to-chromium@1.5.262: {} enhanced-resolve@5.18.3: dependencies: @@ -3327,8 +3280,8 @@ snapshots: '@babel/parser': 7.28.5 eslint: 9.39.1(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.1.12 - zod-validation-error: 4.0.2(zod@4.1.12) + zod: 4.1.13 + zod-validation-error: 4.0.2(zod@4.1.13) transitivePeerDependencies: - supports-color @@ -3352,7 +3305,7 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -3406,22 +3359,10 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -3430,10 +3371,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3453,10 +3390,6 @@ snapshots: get-nonce@1.0.1: {} - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -3494,8 +3427,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-number@7.0.0: {} - isexe@2.0.0: {} jiti@2.6.1: {} @@ -3584,7 +3515,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.554.0(react@19.2.0): + lucide-react@0.555.0(react@19.2.0): dependencies: react: 19.2.0 @@ -3592,13 +3523,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -3647,8 +3571,6 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} - picomatch@4.0.3: {} postcss@8.5.6: @@ -3661,8 +3583,6 @@ snapshots: punycode@2.3.1: {} - queue-microtask@1.2.3: {} - react-dom@19.2.0(react@19.2.0): dependencies: react: 19.2.0 @@ -3670,39 +3590,37 @@ snapshots: react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.6)(react@19.2.0): + react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.0): dependencies: react: 19.2.0 - react-style-singleton: 2.2.3(@types/react@19.2.6)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - react-remove-scroll@2.7.1(@types/react@19.2.6)(react@19.2.0): + react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.2.0): dependencies: react: 19.2.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.6)(react@19.2.0) - react-style-singleton: 2.2.3(@types/react@19.2.6)(react@19.2.0) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.6)(react@19.2.0) - use-sidecar: 1.1.3(@types/react@19.2.6)(react@19.2.0) + use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.0) optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - react-style-singleton@2.2.3(@types/react@19.2.6)(react@19.2.0): + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.0): dependencies: get-nonce: 1.0.1 react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 react@19.2.0: {} resolve-from@4.0.0: {} - reusify@1.1.0: {} - rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -3731,10 +3649,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - scheduler@0.27.0: {} semver@6.3.1: {} @@ -3802,10 +3716,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -3818,12 +3728,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -3843,22 +3753,22 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.2.6)(react@19.2.0): + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.0): dependencies: react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - use-sidecar@1.1.3(@types/react@19.2.6)(react@19.2.0): + use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.0): dependencies: detect-node-es: 1.1.0 react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.6 + '@types/react': 19.2.7 - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2): + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -3882,8 +3792,8 @@ snapshots: yocto-queue@0.1.0: {} - zod-validation-error@4.0.2(zod@4.1.12): + zod-validation-error@4.0.2(zod@4.1.13): dependencies: - zod: 4.1.12 + zod: 4.1.13 - zod@4.1.12: {} + zod@4.1.13: {} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f47e1dd..56d0e95 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,13 +11,14 @@ import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Search, X } from "lucide-react"; import { TooltipProvider } from "@/components/ui/tooltip"; -import { getSettings, applyThemeMode } from "@/lib/settings"; +import { getSettings, applyThemeMode, applyFont } from "@/lib/settings"; import { applyTheme } from "@/lib/themes"; import { OpenFolder } from "../wailsjs/go/main/App"; import { toastWithSound as toast } from "@/lib/toast-with-sound"; // Components import { TitleBar } from "@/components/TitleBar"; +import { Sidebar, type PageType } from "@/components/Sidebar"; import { Header } from "@/components/Header"; import { SearchBar } from "@/components/SearchBar"; import { TrackInfo } from "@/components/TrackInfo"; @@ -27,43 +28,48 @@ import { ArtistInfo } from "@/components/ArtistInfo"; import { DownloadQueue } from "@/components/DownloadQueue"; import { DownloadProgressToast } from "@/components/DownloadProgressToast"; import { AudioAnalysisPage } from "@/components/AudioAnalysisPage"; +import { SettingsPage } from "@/components/SettingsPage"; +import { DebugLoggerPage } from "@/components/DebugLoggerPage"; import type { HistoryItem } from "@/components/FetchHistory"; // Hooks import { useDownload } from "@/hooks/useDownload"; import { useMetadata } from "@/hooks/useMetadata"; import { useLyrics } from "@/hooks/useLyrics"; +import { useCover } from "@/hooks/useCover"; import { useAvailability } from "@/hooks/useAvailability"; import { useDownloadQueueDialog } from "@/hooks/useDownloadQueueDialog"; const HISTORY_KEY = "spotiflac_fetch_history"; const MAX_HISTORY = 5; -type PageType = "main" | "audio-analysis"; - function App() { - const [currentPageView, setCurrentPageView] = useState("main"); + const [currentPage, setCurrentPage] = useState("main"); const [spotifyUrl, setSpotifyUrl] = useState(""); const [selectedTracks, setSelectedTracks] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [sortBy, setSortBy] = useState("default"); - const [currentPage, setCurrentPage] = useState(1); + const [currentListPage, setCurrentListPage] = useState(1); const [hasUpdate, setHasUpdate] = useState(false); + const [releaseDate, setReleaseDate] = useState(null); const [fetchHistory, setFetchHistory] = useState([]); const ITEMS_PER_PAGE = 50; - const CURRENT_VERSION = "6.5"; + const CURRENT_VERSION = "6.6"; const download = useDownload(); const metadata = useMetadata(); const lyrics = useLyrics(); + const cover = useCover(); const availability = useAvailability(); const downloadQueue = useDownloadQueueDialog(); + useEffect(() => { const settings = getSettings(); applyThemeMode(settings.themeMode); applyTheme(settings.theme); + applyFont(settings.fontFamily); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleChange = () => { @@ -88,9 +94,10 @@ function App() { setSearchQuery(""); download.resetDownloadedTracks(); lyrics.resetLyricsState(); + cover.resetCoverState(); availability.clearAvailability(); setSortBy("default"); - setCurrentPage(1); + setCurrentListPage(1); }, [metadata.metadata]); const checkForUpdates = async () => { @@ -99,9 +106,12 @@ function App() { "https://api.github.com/repos/afkarxyz/SpotiFLAC/releases/latest" ); const data = await response.json(); - // tag_name format: "v6.1" -> extract "6.1" const latestVersion = data.tag_name?.replace(/^v/, "") || ""; + if (data.published_at) { + setReleaseDate(data.published_at); + } + if (latestVersion && latestVersion > CURRENT_VERSION) { setHasUpdate(true); } @@ -166,7 +176,6 @@ function App() { } }; - // Add to history when metadata is successfully fetched useEffect(() => { if (!metadata.metadata || !spotifyUrl) return; @@ -217,7 +226,7 @@ function App() { const handleSearchChange = (value: string) => { setSearchQuery(value); - setCurrentPage(1); + setCurrentListPage(1); }; const toggleTrackSelection = (isrc: string) => { @@ -250,6 +259,7 @@ function App() { } }; + const renderMetadata = () => { if (!metadata.metadata) return null; @@ -269,9 +279,11 @@ function App() { skippedLyrics={lyrics.skippedLyrics.has(track.spotify_id || "")} checkingAvailability={availability.checkingTrackId === track.spotify_id} availability={availability.getAvailability(track.spotify_id || "")} + downloadingCover={cover.downloadingCover} onDownload={download.handleDownloadTrack} onDownloadLyrics={lyrics.handleDownloadLyrics} onCheckAvailability={availability.checkAvailability} + onDownloadCover={cover.handleDownloadCover} onOpenFolder={handleOpenFolder} /> ); @@ -294,7 +306,7 @@ function App() { bulkDownloadType={download.bulkDownloadType} downloadProgress={download.downloadProgress} currentDownloadInfo={download.currentDownloadInfo} - currentPage={currentPage} + currentPage={currentListPage} itemsPerPage={ITEMS_PER_PAGE} downloadedLyrics={lyrics.downloadedLyrics} failedLyrics={lyrics.failedLyrics} @@ -302,6 +314,11 @@ function App() { downloadingLyricsTrack={lyrics.downloadingLyricsTrack} checkingAvailabilityTrack={availability.checkingTrackId} availabilityMap={availability.availabilityMap} + downloadedCovers={cover.downloadedCovers} + failedCovers={cover.failedCovers} + skippedCovers={cover.skippedCovers} + downloadingCoverTrack={cover.downloadingCoverTrack} + isBulkDownloadingCovers={cover.isBulkDownloadingCovers} onSearchChange={handleSearchChange} onSortChange={setSortBy} onToggleTrack={toggleTrackSelection} @@ -310,14 +327,18 @@ function App() { onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position) => lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, false, position) } + onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId) => + cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, false, position, trackId) + } onCheckAvailability={availability.checkAvailability} + onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name)} onDownloadAll={() => download.handleDownloadAll(track_list, album_info.name)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, album_info.name) } onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} - onPageChange={setCurrentPage} + onPageChange={setCurrentListPage} onArtistClick={async (artist) => { const artistUrl = await metadata.handleArtistClick(artist); if (artistUrl) { @@ -351,7 +372,7 @@ function App() { bulkDownloadType={download.bulkDownloadType} downloadProgress={download.downloadProgress} currentDownloadInfo={download.currentDownloadInfo} - currentPage={currentPage} + currentPage={currentListPage} itemsPerPage={ITEMS_PER_PAGE} downloadedLyrics={lyrics.downloadedLyrics} failedLyrics={lyrics.failedLyrics} @@ -359,6 +380,11 @@ function App() { downloadingLyricsTrack={lyrics.downloadingLyricsTrack} checkingAvailabilityTrack={availability.checkingTrackId} availabilityMap={availability.availabilityMap} + downloadedCovers={cover.downloadedCovers} + failedCovers={cover.failedCovers} + skippedCovers={cover.skippedCovers} + downloadingCoverTrack={cover.downloadingCoverTrack} + isBulkDownloadingCovers={cover.isBulkDownloadingCovers} onSearchChange={handleSearchChange} onSortChange={setSortBy} onToggleTrack={toggleTrackSelection} @@ -367,7 +393,11 @@ function App() { onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position) => lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, playlist_info.owner.name, false, position) } + onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId) => + cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, playlist_info.owner.name, false, position, trackId) + } onCheckAvailability={availability.checkAvailability} + onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, playlist_info.owner.name)} onDownloadAll={() => download.handleDownloadAll(track_list, playlist_info.owner.name)} onDownloadSelected={() => download.handleDownloadSelected( @@ -378,7 +408,7 @@ function App() { } onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} - onPageChange={setCurrentPage} + onPageChange={setCurrentListPage} onAlbumClick={metadata.handleAlbumClick} onArtistClick={async (artist) => { const artistUrl = await metadata.handleArtistClick(artist); @@ -414,7 +444,7 @@ function App() { bulkDownloadType={download.bulkDownloadType} downloadProgress={download.downloadProgress} currentDownloadInfo={download.currentDownloadInfo} - currentPage={currentPage} + currentPage={currentListPage} itemsPerPage={ITEMS_PER_PAGE} downloadedLyrics={lyrics.downloadedLyrics} failedLyrics={lyrics.failedLyrics} @@ -422,6 +452,11 @@ function App() { downloadingLyricsTrack={lyrics.downloadingLyricsTrack} checkingAvailabilityTrack={availability.checkingTrackId} availabilityMap={availability.availabilityMap} + downloadedCovers={cover.downloadedCovers} + failedCovers={cover.failedCovers} + skippedCovers={cover.skippedCovers} + downloadingCoverTrack={cover.downloadingCoverTrack} + isBulkDownloadingCovers={cover.isBulkDownloadingCovers} onSearchChange={handleSearchChange} onSortChange={setSortBy} onToggleTrack={toggleTrackSelection} @@ -430,7 +465,11 @@ function App() { onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, isArtistDiscography, position) => lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, artist_info.name, isArtistDiscography, position) } + onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, isArtistDiscography, position, trackId) => + cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, artist_info.name, isArtistDiscography, position, trackId) + } onCheckAvailability={availability.checkAvailability} + onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, artist_info.name, true)} onDownloadAll={() => download.handleDownloadAll(track_list, artist_info.name, true)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, artist_info.name, true) @@ -444,7 +483,7 @@ function App() { setSpotifyUrl(artistUrl); } }} - onPageChange={setCurrentPage} + onPageChange={setCurrentListPage} onTrackClick={async (track) => { if (track.external_urls) { setSpotifyUrl(track.external_urls); @@ -458,144 +497,159 @@ function App() { return null; }; + + const renderPage = () => { + switch (currentPage) { + case "settings": + return ; + case "debug": + return ; + case "audio-analysis": + return ; + default: + return ( + <> +
+ + {/* Timeout Dialog */} + + +
+ +
+ Fetch Artist + + Set timeout for fetching metadata. Longer timeout is recommended for artists + with large discography. + + {metadata.pendingArtistName && ( +
+

{metadata.pendingArtistName}

+
+ )} +
+
+ + metadata.setTimeoutValue(Number(e.target.value))} + /> +

+ Default: 60 seconds. For large discographies, try 300-600 seconds (5-10 + minutes). +

+
+
+ + + + +
+
+ + {/* Album Fetch Dialog */} + + +
+ +
+ Fetch Album + + Do you want to fetch metadata for this album? + + {metadata.selectedAlbum && ( +
+

{metadata.selectedAlbum.name}

+
+ )} + + + + +
+
+ + + + {metadata.metadata && renderMetadata()} + + ); + } + }; + return (
-
+ + + {/* Main content area with sidebar offset */} +
- {currentPageView === "audio-analysis" ? ( - setCurrentPageView("main")} /> - ) : ( - <> -
setCurrentPageView("audio-analysis")} - /> - - {/* Download Progress Toast - Bottom Left */} - - - {/* Download Queue Dialog */} - - - {/* Timeout Dialog */} - - -
- -
- Fetch Artist - - Set timeout for fetching metadata. Longer timeout is recommended for artists - with large discography. - - {metadata.pendingArtistName && ( -
-

{metadata.pendingArtistName}

-
- )} -
-
- - metadata.setTimeoutValue(Number(e.target.value))} - /> -

- Default: 60 seconds. For large discographies, try 300-600 seconds (5-10 - minutes). -

-
-
- - - - -
-
- - {/* Album Fetch Dialog */} - - -
- -
- Fetch Album - - Do you want to fetch metadata for this album? - - {metadata.selectedAlbum && ( -
-

{metadata.selectedAlbum.name}

-
- )} - - - - -
-
- - - - {metadata.metadata && renderMetadata()} - - )} + {renderPage()}
+ + {/* Download Progress Toast - Bottom Left */} + + + {/* Download Queue Dialog */} +
); diff --git a/frontend/src/components/AlbumInfo.tsx b/frontend/src/components/AlbumInfo.tsx index 64d08c6..a10ac86 100644 --- a/frontend/src/components/AlbumInfo.tsx +++ b/frontend/src/components/AlbumInfo.tsx @@ -1,7 +1,8 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; -import { Download, FolderOpen } from "lucide-react"; +import { Download, FolderOpen, ImageDown } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { SearchAndSort } from "./SearchAndSort"; import { TrackList } from "./TrackList"; import { DownloadProgress } from "./DownloadProgress"; @@ -39,13 +40,21 @@ interface AlbumInfoProps { // Availability props checkingAvailabilityTrack?: string | null; availabilityMap?: Map; + // Cover props + downloadedCovers?: Set; + failedCovers?: Set; + skippedCovers?: Set; + downloadingCoverTrack?: string | null; + isBulkDownloadingCovers?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (isrc: string) => void; onToggleSelectAll: (tracks: TrackMetadata[]) => void; onDownloadTrack: (isrc: string, name: string, artists: string, albumName: string, spotifyId?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number) => void; + onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string) => void; onCheckAvailability?: (spotifyId: string) => void; + onDownloadAllCovers?: () => void; onDownloadAll: () => void; onDownloadSelected: () => void; onStopDownload: () => void; @@ -77,13 +86,20 @@ export function AlbumInfo({ downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, + downloadedCovers, + failedCovers, + skippedCovers, + downloadingCoverTrack, + isBulkDownloadingCovers, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, + onDownloadCover, onCheckAvailability, + onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, @@ -154,6 +170,22 @@ export function AlbumInfo({ Download Selected ({selectedTracks.length}) )} + {onDownloadAllCovers && ( + + + + + +

Download All Covers

+
+
+ )} {downloadedTracks.size > 0 && ( )} + {onDownloadAllCovers && ( + + + + + +

Download All Covers

+
+
+ )} {downloadedTracks.size > 0 && ( -
-

Audio Quality Analyzer

-

- Analyze FLAC files to verify true lossless quality -

-
+ {onBack && ( + + )} +

Audio Quality Analyzer

{/* File Selection */} {!result && !analyzing && (
= { + info: "text-blue-500", + success: "text-green-500", + warning: "text-yellow-500", + error: "text-red-500", + debug: "text-gray-500", +}; + +function formatTime(date: Date): string { + return date.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} + +export function DebugLoggerPage() { + const [logs, setLogs] = useState([]); + const [copied, setCopied] = useState(false); + const scrollRef = useRef(null); + + useEffect(() => { + const unsubscribe = logger.subscribe(() => { + setLogs(logger.getLogs()); + }); + setLogs(logger.getLogs()); + return () => { + unsubscribe(); + }; + }, []); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [logs]); + + const handleClear = () => { + logger.clear(); + }; + + const handleCopy = async () => { + const logText = logs + .map((log) => `[${formatTime(log.timestamp)}] [${log.level}] ${log.message}`) + .join("\n"); + + try { + await navigator.clipboard.writeText(logText); + setCopied(true); + setTimeout(() => setCopied(false), 500); + } catch (err) { + console.error("Failed to copy logs:", err); + } + }; + + return ( +
+
+

Debug Logs

+
+ + +
+
+ +
+ {logs.length === 0 ? ( +

no logs yet...

+ ) : ( + logs.map((log, i) => ( +
+ + [{formatTime(log.timestamp)}] + + + [{log.level}] + + {log.message} +
+ )) + )} +
+
+ ); +} diff --git a/frontend/src/components/DownloadProgressToast.tsx b/frontend/src/components/DownloadProgressToast.tsx index 85ce305..4499047 100644 --- a/frontend/src/components/DownloadProgressToast.tsx +++ b/frontend/src/components/DownloadProgressToast.tsx @@ -22,7 +22,7 @@ export function DownloadProgressToast({ onClick }: DownloadProgressToastProps) { } return ( -
+
- + + + + + + + {hasUpdate && releaseDate && ( + +

{formatRelativeTime(releaseDate)}

+
+ )} +
{hasUpdate && ( @@ -54,36 +61,6 @@ export function Header({ version, hasUpdate, onOpenAudioAnalysis }: HeaderProps) Get Spotify tracks in true FLAC from Tidal, Deezer, Qobuz & Amazon Music — no account required.

-
- - - - - -

Report bug or request feature

-
-
- - - - - -

Audio Quality Analyzer

-
-
- -
); } diff --git a/frontend/src/components/PlaylistInfo.tsx b/frontend/src/components/PlaylistInfo.tsx index 6ad203f..3c0cfaa 100644 --- a/frontend/src/components/PlaylistInfo.tsx +++ b/frontend/src/components/PlaylistInfo.tsx @@ -1,7 +1,8 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; -import { Download, FolderOpen } from "lucide-react"; +import { Download, FolderOpen, ImageDown } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { SearchAndSort } from "./SearchAndSort"; import { TrackList } from "./TrackList"; import { DownloadProgress } from "./DownloadProgress"; @@ -43,13 +44,21 @@ interface PlaylistInfoProps { // Availability props checkingAvailabilityTrack?: string | null; availabilityMap?: Map; + // Cover props + downloadedCovers?: Set; + failedCovers?: Set; + skippedCovers?: Set; + downloadingCoverTrack?: string | null; + isBulkDownloadingCovers?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (isrc: string) => void; onToggleSelectAll: (tracks: TrackMetadata[]) => void; onDownloadTrack: (isrc: string, name: string, artists: string, albumName: string, spotifyId?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number) => void; + onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string) => void; onCheckAvailability?: (spotifyId: string) => void; + onDownloadAllCovers?: () => void; onDownloadAll: () => void; onDownloadSelected: () => void; onStopDownload: () => void; @@ -82,13 +91,20 @@ export function PlaylistInfo({ downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, + downloadedCovers, + failedCovers, + skippedCovers, + downloadingCoverTrack, + isBulkDownloadingCovers, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, + onDownloadCover, onCheckAvailability, + onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, @@ -145,6 +161,22 @@ export function PlaylistInfo({ Download Selected ({selectedTracks.length}) )} + {onDownloadAllCovers && ( + + + + + +

Download All Covers

+
+
+ )} {downloadedTracks.size > 0 && (
+ + {/* Right Column */} +
+ {/* Filename Format */} +
+ + setTempSettings(prev => ({ ...prev, filenameFormat: value as any }))} + > +
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + {/* Folder Settings */} +
+

Folder Settings

+
+ setTempSettings(prev => ({ ...prev, trackNumber: checked as boolean }))} + /> + + + + + + +

Adds track numbers to filenames

+
+
+
+
+ setTempSettings(prev => ({ ...prev, artistSubfolder: checked as boolean }))} + /> + + + + + + +

Playlist only

+
+
+
+
+ setTempSettings(prev => ({ ...prev, albumSubfolder: checked as boolean }))} + /> + + + + + + +

Playlist & Discography

+
+
+
+
+ +
+ + {/* Sound Effects */} +
+ + + setTempSettings(prev => ({ ...prev, sfxEnabled: checked }))} + /> +
+
+
+ + {/* Actions */} +
+ + +
+
+ ); +} diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx new file mode 100644 index 0000000..0de6efc --- /dev/null +++ b/frontend/src/components/Sidebar.tsx @@ -0,0 +1,86 @@ +import { Home, Settings, Bug, Activity, LayoutGrid } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { openExternal } from "@/lib/utils"; + +export type PageType = "main" | "settings" | "debug" | "audio-analysis"; + +interface SidebarProps { + currentPage: PageType; + onPageChange: (page: PageType) => void; +} + +export function Sidebar({ currentPage, onPageChange }: SidebarProps) { + const navItems = [ + { id: "main" as PageType, icon: Home, label: "Home" }, + { id: "settings" as PageType, icon: Settings, label: "Settings" }, + { id: "audio-analysis" as PageType, icon: Activity, label: "Audio Quality Analyzer" }, + { id: "debug" as PageType, icon: Bug, label: "Debug Logs" }, + ]; + + return ( +
+
+ {navItems.map((item) => ( + + + + + +

{item.label}

+
+
+ ))} +
+ + {/* GitHub - below debug */} + + + + + +

Report Bug

+
+
+ + {/* Other Projects at bottom */} +
+ + + + + +

Other Projects

+
+
+
+
+ ); +} diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 2a2db97..dda3489 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -1,10 +1,7 @@ -import { useState } from "react"; -import { X, Minus, Square } from "lucide-react"; +import { X, Minus, Maximize } from "lucide-react"; import { WindowMinimise, WindowToggleMaximise, Quit } from "../../wailsjs/runtime/runtime"; export function TitleBar() { - const [hoveredButton, setHoveredButton] = useState(null); - const handleMinimize = () => { WindowMinimise(); }; @@ -21,48 +18,36 @@ export function TitleBar() { <> {/* Draggable area */}
- {/* Window control buttons */} -
- + {/* Window control buttons - Windows style, right side */} +
+
diff --git a/frontend/src/components/TrackInfo.tsx b/frontend/src/components/TrackInfo.tsx index ea89390..d0ed0b1 100644 --- a/frontend/src/components/TrackInfo.tsx +++ b/frontend/src/components/TrackInfo.tsx @@ -1,6 +1,7 @@ +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; -import { Download, FolderOpen, CheckCircle, XCircle, FileText, FileCheck, Globe } from "lucide-react"; +import { Download, FolderOpen, CheckCircle, XCircle, FileText, FileCheck, Globe, ImageDown } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, @@ -23,9 +24,11 @@ interface TrackInfoProps { skippedLyrics?: boolean; checkingAvailability?: boolean; availability?: TrackAvailability; + downloadingCover?: boolean; onDownload: (isrc: string, name: string, artists: string, albumName?: string, spotifyId?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName?: string) => void; onCheckAvailability?: (spotifyId: string, isrc?: string) => void; + onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName?: string) => void; onOpenFolder: () => void; } @@ -42,22 +45,52 @@ export function TrackInfo({ skippedLyrics, checkingAvailability, availability, + downloadingCover, onDownload, onDownloadLyrics, onCheckAvailability, + onDownloadCover, onOpenFolder, }: TrackInfoProps) { + const [isHoveringCover, setIsHoveringCover] = useState(false); + return (
-
+
setIsHoveringCover(true)} + onMouseLeave={() => setIsHoveringCover(false)} + > {track.images && ( - {track.name} + <> + {track.name} + {isHoveringCover && onDownloadCover && ( +
+ + + + + +

Download Cover

+
+
+
+ )} + )}
@@ -99,6 +132,32 @@ export function TrackInfo({ )} + {track.spotify_id && onDownloadLyrics && ( + + + + + +

Download Lyric

+
+
+ )} {track.spotify_id && onCheckAvailability && ( @@ -130,32 +189,6 @@ export function TrackInfo({ )} - {track.spotify_id && onDownloadLyrics && ( - - - - - -

Download Lyric

-
-
- )} {isDownloaded && ( - )} - {track.spotify_id && onCheckAvailability && ( - {availabilityMap?.has(track.spotify_id) ? ( -
- - - - -
+ {downloadingTrack === track.isrc ? ( +

Downloading...

+ ) : skippedTracks.has(track.isrc) ? ( +

Already exists

+ ) : downloadedTracks.has(track.isrc) ? ( +

Downloaded

+ ) : failedTracks.has(track.isrc) ? ( +

Failed

) : ( -

Check Availability

+

Download Track

)}
@@ -365,6 +363,68 @@ export function TrackList({ )} + {track.images && onDownloadCover && ( + + + + + +

Download Cover

+
+
+ )} + {track.spotify_id && onCheckAvailability && ( + + + + + + {availabilityMap?.has(track.spotify_id) ? ( +
+ + + + +
+ ) : ( +

Check Availability

+ )} +
+
+ )}
diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx index b983c9a..732f077 100644 --- a/frontend/src/components/ui/sonner.tsx +++ b/frontend/src/components/ui/sonner.tsx @@ -36,6 +36,7 @@ const Toaster = ({ ...props }: ToasterProps) => { "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", "--border-radius": "var(--radius)", + left: "calc(56px + 1rem)", } as React.CSSProperties } {...props} diff --git a/frontend/src/components/ui/switch.tsx b/frontend/src/components/ui/switch.tsx new file mode 100644 index 0000000..8ac5f16 --- /dev/null +++ b/frontend/src/components/ui/switch.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitive from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +function Switch({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Switch } diff --git a/frontend/src/hooks/useCover.ts b/frontend/src/hooks/useCover.ts new file mode 100644 index 0000000..fe0d623 --- /dev/null +++ b/frontend/src/hooks/useCover.ts @@ -0,0 +1,218 @@ +import { useState, useRef } from "react"; +import { downloadCover } from "@/lib/api"; +import { getSettings } from "@/lib/settings"; +import { toastWithSound as toast } from "@/lib/toast-with-sound"; +import { joinPath, sanitizePath } from "@/lib/utils"; +import { logger } from "@/lib/logger"; +import type { TrackMetadata } from "@/types/api"; + +export function useCover() { + const [downloadingCover, setDownloadingCover] = useState(false); + const [downloadingCoverTrack, setDownloadingCoverTrack] = useState(null); + const [downloadedCovers, setDownloadedCovers] = useState>(new Set()); + const [failedCovers, setFailedCovers] = useState>(new Set()); + const [skippedCovers, setSkippedCovers] = useState>(new Set()); + const [isBulkDownloadingCovers, setIsBulkDownloadingCovers] = useState(false); + const [coverDownloadProgress, setCoverDownloadProgress] = useState(0); + const stopBulkDownloadRef = useRef(false); + + const handleDownloadCover = async ( + coverUrl: string, + trackName: string, + artistName: string, + albumName?: string, + playlistName?: string, + isArtistDiscography?: boolean, + position?: number, + trackId?: string + ) => { + if (!coverUrl) { + toast.error("No cover URL found for this track"); + return; + } + + const id = trackId || `${trackName}-${artistName}`; + logger.info(`downloading cover: ${trackName} - ${artistName}`); + const settings = getSettings(); + setDownloadingCover(true); + setDownloadingCoverTrack(id); + + try { + const os = settings.operatingSystem; + let outputDir = settings.downloadPath; + + // Build output path similar to audio download + if (playlistName) { + outputDir = joinPath(os, outputDir, sanitizePath(playlistName, os)); + + if (isArtistDiscography) { + if (settings.albumSubfolder && albumName) { + outputDir = joinPath(os, outputDir, sanitizePath(albumName, os)); + } + } else { + if (settings.artistSubfolder && artistName) { + outputDir = joinPath(os, outputDir, sanitizePath(artistName, os)); + } + if (settings.albumSubfolder && albumName) { + outputDir = joinPath(os, outputDir, sanitizePath(albumName, os)); + } + } + } + + const response = await downloadCover({ + cover_url: coverUrl, + track_name: trackName, + artist_name: artistName, + output_dir: outputDir, + filename_format: settings.filenameFormat, + track_number: settings.trackNumber, + position: position || 0, + }); + + if (response.success) { + if (response.already_exists) { + toast.info("Cover file already exists"); + setSkippedCovers((prev) => new Set(prev).add(id)); + } else { + toast.success("Cover downloaded successfully"); + setDownloadedCovers((prev) => new Set(prev).add(id)); + } + setFailedCovers((prev) => { + const newSet = new Set(prev); + newSet.delete(id); + return newSet; + }); + } else { + toast.error(response.error || "Failed to download cover"); + setFailedCovers((prev) => new Set(prev).add(id)); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : "Failed to download cover"); + setFailedCovers((prev) => new Set(prev).add(id)); + } finally { + setDownloadingCover(false); + setDownloadingCoverTrack(null); + } + }; + + const handleDownloadAllCovers = async ( + tracks: TrackMetadata[], + playlistName?: string, + isArtistDiscography?: boolean + ) => { + if (tracks.length === 0) { + toast.error("No tracks to download covers"); + return; + } + + const settings = getSettings(); + setIsBulkDownloadingCovers(true); + setCoverDownloadProgress(0); + stopBulkDownloadRef.current = false; + + let completed = 0; + let success = 0; + let skipped = 0; + let failed = 0; + + for (let i = 0; i < tracks.length; i++) { + if (stopBulkDownloadRef.current) { + toast.info("Cover download stopped"); + break; + } + + const track = tracks[i]; + if (!track.images) { + completed++; + setCoverDownloadProgress(Math.round((completed / tracks.length) * 100)); + continue; + } + + const id = track.spotify_id || `${track.name}-${track.artists}`; + setDownloadingCoverTrack(id); + + try { + const os = settings.operatingSystem; + let outputDir = settings.downloadPath; + + if (playlistName) { + outputDir = joinPath(os, outputDir, sanitizePath(playlistName, os)); + + if (isArtistDiscography) { + if (settings.albumSubfolder && track.album_name) { + outputDir = joinPath(os, outputDir, sanitizePath(track.album_name, os)); + } + } else { + if (settings.artistSubfolder && track.artists) { + outputDir = joinPath(os, outputDir, sanitizePath(track.artists, os)); + } + if (settings.albumSubfolder && track.album_name) { + outputDir = joinPath(os, outputDir, sanitizePath(track.album_name, os)); + } + } + } + + const response = await downloadCover({ + cover_url: track.images, + track_name: track.name, + artist_name: track.artists, + output_dir: outputDir, + filename_format: settings.filenameFormat, + track_number: settings.trackNumber, + position: i + 1, + }); + + if (response.success) { + if (response.already_exists) { + skipped++; + setSkippedCovers((prev) => new Set(prev).add(id)); + } else { + success++; + setDownloadedCovers((prev) => new Set(prev).add(id)); + } + } else { + failed++; + setFailedCovers((prev) => new Set(prev).add(id)); + } + } catch { + failed++; + setFailedCovers((prev) => new Set(prev).add(id)); + } + + completed++; + setCoverDownloadProgress(Math.round((completed / tracks.length) * 100)); + } + + setDownloadingCoverTrack(null); + setIsBulkDownloadingCovers(false); + setCoverDownloadProgress(0); + + if (!stopBulkDownloadRef.current) { + toast.success(`Covers: ${success} downloaded, ${skipped} skipped, ${failed} failed`); + } + }; + + const handleStopCoverDownload = () => { + stopBulkDownloadRef.current = true; + }; + + const resetCoverState = () => { + setDownloadedCovers(new Set()); + setFailedCovers(new Set()); + setSkippedCovers(new Set()); + }; + + return { + downloadingCover, + downloadingCoverTrack, + downloadedCovers, + failedCovers, + skippedCovers, + isBulkDownloadingCovers, + coverDownloadProgress, + handleDownloadCover, + handleDownloadAllCovers, + handleStopCoverDownload, + resetCoverState, + }; +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index c39f244..c386650 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -5,8 +5,10 @@ import type { HealthResponse, LyricsDownloadRequest, LyricsDownloadResponse, + CoverDownloadRequest, + CoverDownloadResponse, } from "@/types/api"; -import { GetSpotifyMetadata, DownloadTrack, DownloadLyrics } from "../../wailsjs/go/main/App"; +import { GetSpotifyMetadata, DownloadTrack, DownloadLyrics, DownloadCover } from "../../wailsjs/go/main/App"; import { main } from "../../wailsjs/go/models"; export async function fetchSpotifyMetadata( @@ -48,3 +50,10 @@ export async function downloadLyrics( const req = new main.LyricsDownloadRequest(request); return await DownloadLyrics(req); } + +export async function downloadCover( + request: CoverDownloadRequest +): Promise { + const req = new main.CoverDownloadRequest(request); + return await DownloadCover(req); +} diff --git a/frontend/src/lib/relative-time.ts b/frontend/src/lib/relative-time.ts new file mode 100644 index 0000000..46cb4cc --- /dev/null +++ b/frontend/src/lib/relative-time.ts @@ -0,0 +1,59 @@ +/** + * Format a date to relative time string with max 2 units + * e.g., "23 hours 32 minutes ago", "1 day 14 hours ago" + */ +export function formatRelativeTime(date: Date | string | number): string { + const now = new Date(); + const target = new Date(date); + const diffMs = now.getTime() - target.getTime(); + + if (diffMs < 0) return "just now"; + + const seconds = Math.floor(diffMs / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const weeks = Math.floor(days / 7); + const months = Math.floor(days / 30); + const years = Math.floor(days / 365); + + const parts: string[] = []; + + if (years > 0) { + parts.push(`${years} ${years === 1 ? "year" : "years"}`); + const remainingMonths = Math.floor((days % 365) / 30); + if (remainingMonths > 0) { + parts.push(`${remainingMonths} ${remainingMonths === 1 ? "month" : "months"}`); + } + } else if (months > 0) { + parts.push(`${months} ${months === 1 ? "month" : "months"}`); + const remainingDays = days % 30; + if (remainingDays > 0) { + parts.push(`${remainingDays} ${remainingDays === 1 ? "day" : "days"}`); + } + } else if (weeks > 0) { + parts.push(`${weeks} ${weeks === 1 ? "week" : "weeks"}`); + const remainingDays = days % 7; + if (remainingDays > 0) { + parts.push(`${remainingDays} ${remainingDays === 1 ? "day" : "days"}`); + } + } else if (days > 0) { + parts.push(`${days} ${days === 1 ? "day" : "days"}`); + const remainingHours = hours % 24; + if (remainingHours > 0) { + parts.push(`${remainingHours} ${remainingHours === 1 ? "hour" : "hours"}`); + } + } else if (hours > 0) { + parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`); + const remainingMinutes = minutes % 60; + if (remainingMinutes > 0) { + parts.push(`${remainingMinutes} ${remainingMinutes === 1 ? "minute" : "minutes"}`); + } + } else if (minutes > 0) { + parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`); + } else { + return "just now"; + } + + return "Released " + parts.slice(0, 2).join(" ") + " ago"; +} diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts index 341f063..1f13763 100644 --- a/frontend/src/lib/settings.ts +++ b/frontend/src/lib/settings.ts @@ -1,14 +1,18 @@ import { GetDefaults } from "../../wailsjs/go/main/App"; +export type FontFamily = "google-sans" | "inter" | "poppins" | "roboto" | "dm-sans" | "plus-jakarta-sans" | "manrope" | "space-grotesk"; + export interface Settings { downloadPath: string; downloader: "auto" | "deezer" | "tidal" | "qobuz" | "amazon"; theme: string; themeMode: "auto" | "light" | "dark"; + fontFamily: FontFamily; filenameFormat: "title-artist" | "artist-title" | "title"; artistSubfolder: boolean; albumSubfolder: boolean; trackNumber: boolean; + sfxEnabled: boolean; operatingSystem: "Windows" | "linux/MacOS" } @@ -26,13 +30,34 @@ export const DEFAULT_SETTINGS: Settings = { downloader: "auto", theme: "yellow", themeMode: "auto", + fontFamily: "google-sans", filenameFormat: "title-artist", artistSubfolder: false, albumSubfolder: false, trackNumber: false, + sfxEnabled: true, operatingSystem: detectOS() }; +export const FONT_OPTIONS: { value: FontFamily; label: string; fontFamily: string }[] = [ + { value: "google-sans", label: "Google Sans Flex", fontFamily: '"Google Sans Flex", system-ui, sans-serif' }, + { value: "inter", label: "Inter", fontFamily: '"Inter", system-ui, sans-serif' }, + { value: "poppins", label: "Poppins", fontFamily: '"Poppins", system-ui, sans-serif' }, + { value: "roboto", label: "Roboto", fontFamily: '"Roboto", system-ui, sans-serif' }, + { value: "dm-sans", label: "DM Sans", fontFamily: '"DM Sans", system-ui, sans-serif' }, + { value: "plus-jakarta-sans", label: "Plus Jakarta Sans", fontFamily: '"Plus Jakarta Sans", system-ui, sans-serif' }, + { value: "manrope", label: "Manrope", fontFamily: '"Manrope", system-ui, sans-serif' }, + { value: "space-grotesk", label: "Space Grotesk", fontFamily: '"Space Grotesk", system-ui, sans-serif' }, +]; + +export function applyFont(fontFamily: FontFamily): void { + const font = FONT_OPTIONS.find(f => f.value === fontFamily); + if (font) { + document.documentElement.style.setProperty('--font-sans', font.fontFamily); + document.body.style.fontFamily = font.fontFamily; + } +} + async function fetchDefaultPath(): Promise { try { const data = await GetDefaults(); diff --git a/frontend/src/lib/toast-with-sound.ts b/frontend/src/lib/toast-with-sound.ts index 20ef5b3..840eb50 100644 --- a/frontend/src/lib/toast-with-sound.ts +++ b/frontend/src/lib/toast-with-sound.ts @@ -6,38 +6,42 @@ import { playInfoSound, } from "./audio"; import { logger } from "./logger"; +import { getSettings } from "./settings"; const toastStyle = { className: "font-mono lowercase", }; +// Helper to check if SFX is enabled +const isSfxEnabled = () => getSettings().sfxEnabled; + // Wrapper functions for toast with sound effects export const toastWithSound = { success: (message: string, data?: any) => { const msg = message.toLowerCase(); logger.success(msg); - playSuccessSound(); + if (isSfxEnabled()) playSuccessSound(); return toast.success(msg, { ...toastStyle, ...data }); }, error: (message: string, data?: any) => { const msg = message.toLowerCase(); logger.error(msg); - playErrorSound(); + if (isSfxEnabled()) playErrorSound(); return toast.error(msg, { ...toastStyle, ...data }); }, warning: (message: string, data?: any) => { const msg = message.toLowerCase(); logger.warning(msg); - playWarningSound(); + if (isSfxEnabled()) playWarningSound(); return toast.warning(msg, { ...toastStyle, ...data }); }, info: (message: string, data?: any) => { const msg = message.toLowerCase(); logger.info(msg); - playInfoSound(); + if (isSfxEnabled()) playInfoSound(); return toast.info(msg, { ...toastStyle, ...data }); }, @@ -45,7 +49,7 @@ export const toastWithSound = { message: (message: string, data?: any) => { const msg = message.toLowerCase(); logger.info(msg); - playInfoSound(); + if (isSfxEnabled()) playInfoSound(); return toast(msg, { ...toastStyle, ...data }); }, }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 188050e..d9181e8 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,14 +3,10 @@ import { createRoot } from "react-dom/client"; import "./index.css"; import App from "./App.tsx"; import { Toaster } from "@/components/ui/sonner"; -import { DebugLogger } from "@/components/DebugLogger"; createRoot(document.getElementById("root")!).render( -
- -
); diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 94e5adc..16f77c1 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -200,4 +200,22 @@ export interface TrackAvailability { qobuz_url?: string; } +export interface CoverDownloadRequest { + cover_url: string; + track_name: string; + artist_name: string; + output_dir?: string; + filename_format?: string; + track_number?: boolean; + position?: number; +} + +export interface CoverDownloadResponse { + success: boolean; + message: string; + file?: string; + error?: string; + already_exists?: boolean; +} + diff --git a/version.json b/version.json index a0629a8..327a8e0 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "6.5" + "version": "6.6" } diff --git a/wails.json b/wails.json index 4ef9f96..d265b9c 100644 --- a/wails.json +++ b/wails.json @@ -12,7 +12,7 @@ }, "info": { "productName": "SpotiFLAC", - "productVersion": "6.5" + "productVersion": "6.6" }, "wailsjsdir": "./frontend", "assetdir": "./frontend/dist",