From c5cb6f29b6d6ca1e002658628a7f894915f57b64 Mon Sep 17 00:00:00 2001 From: Markury Date: Wed, 1 Oct 2025 11:46:02 -0400 Subject: [PATCH] fix(ui): ensure proper flexbox layout by adding min-height constraints --- CLAUDE.md | 226 ++++++++++++++++++++++++++++++++ src/routes/library/+page.svelte | 4 +- 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1f1132a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,226 @@ +# 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. diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte index 7480d45..d9ac436 100644 --- a/src/routes/library/+page.svelte +++ b/src/routes/library/+page.svelte @@ -201,6 +201,7 @@ flex: 1; display: flex; flex-direction: column; + min-height: 0; } .window-body { @@ -208,12 +209,13 @@ flex: 1; display: flex; flex-direction: column; - overflow: hidden; + min-height: 0; } .table-container { flex: 1; overflow-y: auto; + min-height: 0; } table {