# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Shark is a music management and download application built with Tauri 2, SvelteKit 2, and TypeScript. It integrates with Deezer for music downloads and uses a custom Windows 98-style UI theme. ## Development Commands - **Run dev server**: `bun run tauri dev` (runs both Vite dev server and Tauri) - **Build for production**: `bun run tauri build` - **Type checking**: `bun run check` - **Add packages**: `bun add ` ### Important Notes - Always use `bun` as the package manager, not npm or yarn - Tauri capabilities changes require a full restart of `bun run tauri dev` to take effect - The app uses custom window decorations (no native title bar) ## Architecture ### Tech Stack - **Frontend**: SvelteKit 2 with Svelte 5 (runes-based reactivity) - **Desktop Runtime**: Tauri 2 - **Package Manager**: Bun - **Styling**: Custom Windows 98 theme (defined in `+layout.svelte`) ### Directory Structure ``` shark/ ├── src/ # SvelteKit frontend │ ├── routes/ # File-based routing │ │ ├── +layout.svelte # Root layout with Windows 98 theme │ │ ├── library/ # Music library management │ │ ├── playlists/ # Playlist management │ │ ├── services/deezer/ # Deezer integration UI │ │ └── settings/ # App settings │ └── lib/ │ ├── services/ │ │ └── deezer/ # Deezer service layer │ │ ├── crypto.ts # Blowfish decryption for tracks │ │ ├── downloader.ts # Track download & decryption │ │ └── paths.ts # File path generation │ ├── stores/ # Svelte stores (reactive state) │ │ ├── deezer.ts # Deezer auth & user state │ │ └── settings.ts # App settings state │ └── types/ # TypeScript type definitions └── src-tauri/ # Tauri backend ├── capabilities/ │ └── default.json # Security permissions └── tauri.conf.json # Tauri configuration ``` ### Key Components **UI Components** (`src/lib/`): - `MenuBar.svelte` - Top menu bar - `TitleBar.svelte` - Custom window title bar with controls - `ToolBar.svelte` - Navigation toolbar **Services** (`src/lib/services/`): - `deezer.ts` - Main Deezer API client with cookie-based session management - `deezer/crypto.ts` - MD5, AES-ECB, and Blowfish decryption utilities - `deezer/downloader.ts` - Handles streaming download and chunk-based Blowfish decryption - `deezer/paths.ts` - Generates file paths with template: `// - ` **State Management**: - Uses Tauri's `@tauri-apps/plugin-store` for persistent storage - Svelte stores for reactive state (`$state` runes in Svelte 5) - Deezer authentication stored persistently (ARL token + user data) ### Deezer Integration Details **Authentication**: - Uses ARL (Authentication Resource Locator) cookie from Deezer website - Implements manual cookie jar to maintain session across requests - CSRF tokens are dynamically refreshed (stored in `DeezerAPI.apiToken`) **API Architecture**: - **Gateway API** (`deezer.gw`): Used for track metadata via `http://www.deezer.com/ajax/gw-light.php` - **Media API**: Used for download URLs via `https://media.deezer.com/v1/get_url` - **CDN**: Actual track downloads from `*.dzcdn.net` domains **Cookie Management** (Critical): - Deezer requires both ARL cookie AND session cookies (sid) from API responses - `DeezerAPI` class implements a `Map<string, string>` cookie store - All cookies are sent with every request to maintain session state - This is essential because Deezer's CSRF tokens are session-dependent **Decryption Flow**: 1. Download encrypted FLAC/MP3 from CDN 2. Every 3rd chunk of 2048 bytes is Blowfish-CBC encrypted 3. Generate Blowfish key from track ID using MD5 XOR algorithm 4. Decrypt chunks using `blowfish-node` library (browser-compatible mode) 5. Write decrypted file to user-selected music folder **Crypto Implementation**: - `md5()` - Custom MD5 implementation (pure JS, no Node.js crypto) - `ecbEncrypt()` - AES-128-ECB using `@noble/ciphers` - `decryptChunk()` - Blowfish-CBC using `blowfish-node` with Uint8Array (not Buffer) - `generateBlowfishKey()` - Generates key from track ID + secret ### Tauri Security Model **Capabilities** (`src-tauri/capabilities/default.json`): - HTTP requests are scoped to allowed domains only - Required domains: `*.deezer.com`, `media.deezer.com`, `*.dzcdn.net` - File system access is scoped (currently `**` for user-selected directories) - Uses `unsafe-headers` feature to send custom Cookie headers **Permissions Required**: - `fs:allow-write-file` - Write downloaded tracks - `fs:allow-mkdir` - Create artist/album folders - `fs:allow-remove` - Clean up temp files - `fs:allow-rename` - Move from temp to final location - `http:default` with custom scope - Fetch from Deezer APIs/CDN ### Common Patterns **Adding New Tauri Permissions**: 1. Edit `src-tauri/capabilities/default.json` 2. Add specific permission (e.g., `"fs:allow-read-file"`) 3. Add scope if needed (e.g., URL patterns for HTTP) 4. **Must restart** `bun run tauri dev` completely (stop and start) **Deezer API Calls**: - Always use `DeezerAPI.apiCall()` for GW API endpoints - Pass method name and args object - Automatic CSRF token refresh with retry logic (max 2 retries) - Cookie headers are automatically managed - Rate limiting handled automatically (error codes 4, 700 → 5s wait + retry) - Network errors (ECONNRESET, ETIMEDOUT, etc.) → 2s wait + retry - Download timeout (60s) with automatic retry (max 3 attempts) **Svelte 5 State**: - Use `$state` rune for reactive variables - Use `$derived` for computed values - Stores use `$` prefix in templates (e.g., `$deezerAuth.user`) ### Known Issues & Solutions **Infinite Loop Prevention**: - Token refresh has retry limit (2 attempts) to prevent infinite loops - Chunk decryption errors are sampled (1% logging) to prevent console spam **Error Handling & Resilience**: - All network operations have automatic retry logic matching deemix behavior - API rate limiting detected and handled (5s wait on error codes 4, 700) - Network timeouts/errors trigger 2s wait + retry - Download timeouts (60s) with up to 3 retry attempts - Maintains deemix-compliant behavior to avoid bans **Browser Compatibility**: - Must use `Uint8Array` instead of Node.js `Buffer` - Blowfish IV must be `Uint8Array`, not Buffer - `@noble/ciphers` for AES (browser-compatible) - `blowfish-node` works in browser when using Uint8Array mode **File Path Sanitization**: - Uses `paths.ts` to sanitize illegal characters for cross-platform compatibility - Handles Windows reserved names (CON, PRN, etc.) - Template is hard-coded but can be extended ## Testing Deezer Integration 1. **Get ARL Token**: - Log into deezer.com in browser - Open DevTools → Application → Cookies - Copy the `arl` cookie value (192 chars) 2. **Test Track Download**: - Navigate to Services → Deezer - Enter ARL token and login - Use track ID `3135556` (Daft Punk - One More Time) for testing - Check console for `[DEBUG]` logs showing API flow 3. **Verify Download**: - Check that FLAC/MP3 is created in music folder - Verify file is playable (decryption worked) - Check folder structure matches template ## Dependencies Note - `@noble/ciphers` - AES encryption (browser-compatible) - `blowfish-node` - Blowfish decryption (works in browser with Uint8Array) - `browser-id3-writer` - ID3 tag writing - `music-metadata` - Metadata reading - `@tauri-apps/plugin-*` - Tauri official plugins Do not use Node.js `crypto` or `Buffer` - these don't work in browser/Tauri frontend. ## UI/Layout Tips ### Perfectly Nesting Content Without Page Scroll When content needs to fit perfectly within a page container without creating scrollbars: **DON'T:** Use magic numbers with `calc(100vh - ???px)` - this requires guessing exact pixel values **DO:** Use flexbox with proper height constraints: ```css .wrapper { height: 100%; display: flex; flex-direction: column; } .header { flex-shrink: 0; /* Takes natural height */ } .content { flex: 1; /* Fills remaining space */ display: flex; flex-direction: column; min-height: 0; /* Allows proper shrinking */ } .scrollable-area { flex: 1; overflow-y: auto; } ``` This automatically calculates the correct heights without hardcoded values.