mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 11:41:02 +00:00
fix(ui): ensure proper flexbox layout by adding min-height constraints
This commit is contained in:
226
CLAUDE.md
Normal file
226
CLAUDE.md
Normal file
@@ -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 <package-name>`
|
||||||
|
|
||||||
|
### 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: `<albumartist>/<album>/<tracknumber> - <title>`
|
||||||
|
|
||||||
|
**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.
|
||||||
@@ -201,6 +201,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.window-body {
|
.window-body {
|
||||||
@@ -208,12 +209,13 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|||||||
Reference in New Issue
Block a user