|
| 1 | +# Raga — Claude Code Guide |
| 2 | + |
| 3 | +Raga is a music library management suite for DJs. It imports music libraries from |
| 4 | +[Swinsian](https://swinsian.com/), analyzes tracks (BPM, metadata), and exports |
| 5 | +libraries to formats compatible with [Rekordbox](https://rekordbox.com/) (Pioneer's |
| 6 | +industry-standard DJ software). It ships as an Electron desktop app, a portable web |
| 7 | +UI, a core Node.js library, and a standalone CLI. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Monorepo Package Overview |
| 12 | + |
| 13 | +This is a Yarn v4 workspace monorepo managed with Nx (task orchestration) and Lerna |
| 14 | +(versioning). All packages live under `packages/`. |
| 15 | + |
| 16 | +| Package | Purpose | Published | |
| 17 | +|---------|---------|-----------| |
| 18 | +| `raga-types` | Shared TypeScript interfaces + IPC event channel definitions | No | |
| 19 | +| `raga-lib` | Core data-transformation library (plist parsing, FFmpeg conversion) | Yes (npm) | |
| 20 | +| `raga-web-app` | React/Vite UI — runs standalone in browser or embedded in Electron | No | |
| 21 | +| `raga-app` | Electron desktop app — wraps raga-web-app, adds IPC + file I/O | No | |
| 22 | +| `raga-cli` | CLI tool for Swinsian → Rekordbox batch conversion | No | |
| 23 | + |
| 24 | +**Dependency order:** `raga-types` ← `raga-lib` ← `raga-app` / `raga-cli`; `raga-web-app` uses `raga-types`. |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Prerequisites |
| 29 | + |
| 30 | +```bash |
| 31 | +node --version # Must be v24.x (see .nvmrc; use `nvm use` or corepack) |
| 32 | +yarn --version # v4.x (enabled via `corepack enable`) |
| 33 | +ffmpeg -version # Required for audio conversion features |
| 34 | +deno --version # v2.x — only needed to build the raga-cli standalone binary |
| 35 | +``` |
| 36 | + |
| 37 | +Install dependencies: |
| 38 | + |
| 39 | +```bash |
| 40 | +corepack enable |
| 41 | +yarn install |
| 42 | +``` |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Development Commands |
| 47 | + |
| 48 | +All commands run from the repo root via Nx task orchestration: |
| 49 | + |
| 50 | +```bash |
| 51 | +# Start dev servers |
| 52 | +yarn dev:web # Vite dev server for raga-web-app at http://localhost:3000 |
| 53 | +yarn dev:electron # Full Electron app (also launches react-devtools) |
| 54 | + |
| 55 | +# Build |
| 56 | +yarn build # TypeScript compilation for all packages (tsc) |
| 57 | +yarn dist # Package Electron app as distributable (.dmg / .deb) |
| 58 | + |
| 59 | +# Type checking & linting |
| 60 | +yarn check-types # TypeScript type check (all packages) |
| 61 | +yarn check-lint # ESLint check (all packages) |
| 62 | +yarn fix-lint # Auto-fix ESLint issues |
| 63 | +yarn check-format # Prettier format check |
| 64 | +yarn fix-format # Auto-format with Prettier |
| 65 | + |
| 66 | +# Testing |
| 67 | +yarn test # Run all Vitest suites |
| 68 | +``` |
| 69 | + |
| 70 | +Run checks before committing: `yarn check-types && yarn check-lint && yarn check-format`. |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +## Architecture |
| 75 | + |
| 76 | +### Electron Process Model |
| 77 | + |
| 78 | +`raga-app` uses three separate OS processes: |
| 79 | + |
| 80 | +1. **Main process** (`src/main.ts`) — creates the browser window, routes IPC messages. |
| 81 | +2. **Utility process** (`src/server.ts`) — runs `@tinyhttp/app` HTTP server; handles |
| 82 | + CPU-heavy work: plist parsing, FFmpeg conversion, ID3 tag writing, Discogs API calls. |
| 83 | +3. **Renderer process** — the compiled `raga-web-app` React bundle. |
| 84 | + |
| 85 | +``` |
| 86 | +Renderer (React UI) |
| 87 | + ↕ window.api (context bridge) |
| 88 | +Main process |
| 89 | + ↕ MessageChannelMain / IPC |
| 90 | +Utility process (HTTP server on localhost) |
| 91 | +``` |
| 92 | + |
| 93 | +The preload script (`src/preload.ts`) exposes `window.api` to the renderer via |
| 94 | +`contextBridge`. It queues outgoing events until the utility process signals it is |
| 95 | +ready (`APP_SERVER_PING` / `APP_SERVER_READY` handshake). |
| 96 | + |
| 97 | +### IPC Event System |
| 98 | + |
| 99 | +Event channels and payloads are defined in `raga-types`: |
| 100 | + |
| 101 | +- **Client events** (renderer → server): `packages/raga-types/src/api/clientEvents.ts` |
| 102 | +- **Server events** (server → renderer): `packages/raga-types/src/api/serverEvents.ts` |
| 103 | + |
| 104 | +Channel names use camelCase string literals collected in the `ClientEventChannel` and |
| 105 | +`ServerEventChannel` const objects. Always add new events there first before |
| 106 | +implementing handlers. |
| 107 | + |
| 108 | +### Web App Dual-Mode |
| 109 | + |
| 110 | +`raga-web-app` detects its host environment at runtime via `window.api`: |
| 111 | + |
| 112 | +- **Electron mode** — `window.api` is present; full feature set via IPC. |
| 113 | +- **Standalone mode** — `window.api` is absent; falls back to `src/webApi.ts` which |
| 114 | + provides mock data and no-op stubs (useful for browser-only development). |
| 115 | + |
| 116 | +### State Management |
| 117 | + |
| 118 | +`raga-web-app` uses **Zustand** with an immer + persist middleware stack: |
| 119 | + |
| 120 | +- Root store: `packages/raga-web-app/src/store/appStore.ts` |
| 121 | +- State is split into slices under `src/store/slices/` (one file per domain) |
| 122 | +- Use the `createSelectors` utility for memoized per-key subscriptions — do not |
| 123 | + subscribe to the entire store object |
| 124 | +- Persisted state uses localStorage with a schema version key |
| 125 | + |
| 126 | +### Async Patterns (Electron server) |
| 127 | + |
| 128 | +Complex multi-step async operations in the utility process use the **Effection** |
| 129 | +generator pattern: |
| 130 | + |
| 131 | +```ts |
| 132 | +import { run } from "effection"; |
| 133 | +yield* run(function* () { /* ... */ }); |
| 134 | +``` |
| 135 | + |
| 136 | +Use Effection for operations that need cancellation, timeouts, or structured |
| 137 | +concurrency. Simple one-shot async calls can use plain `async/await`. |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Key Domain Concepts |
| 142 | + |
| 143 | +| Term | Meaning | |
| 144 | +|------|---------| |
| 145 | +| **Swinsian library** | A plist XML file exported from the Swinsian music player containing tracks and playlists | |
| 146 | +| **TrackDefinition** | The core track data structure (see `raga-types`); has ~25 fields: Track ID, Persistent ID, Location, BPM, Rating, Artist, Album, Genre, etc. | |
| 147 | +| **Persistent ID** | Hex-encoded unique identifier for a track, used by both Swinsian and Music.app/Rekordbox | |
| 148 | +| **Music.app / iTunes XML** | The plist format that Rekordbox ingests; raga converts Swinsian libraries into this format | |
| 149 | +| **BPM** | Beats per minute; analyzed via `web-audio-beat-detector` in the browser or stored as ID3 metadata | |
| 150 | +| **ID3 tags** | Metadata embedded in MP3 files; raga writes them via `node-taglib-sharp` | |
| 151 | +| **Audio Files Server** | The HTTP server in the utility process that serves converted MP3s to the web audio player | |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Code Style & Conventions |
| 156 | + |
| 157 | +These are enforced by ESLint + Prettier; the CI will catch violations. |
| 158 | + |
| 159 | +### TypeScript |
| 160 | + |
| 161 | +- Strict mode is on everywhere. Avoid `any`. |
| 162 | +- Use the `type` keyword for type-only imports: `import type { Foo } from "./foo.js"`. |
| 163 | +- Import order is auto-managed by `simple-import-sort` — don't manually reorder imports. |
| 164 | +- `@typescript-eslint/consistent-type-imports` is enforced; run `yarn fix-lint` to auto-fix. |
| 165 | + |
| 166 | +### React / UI |
| 167 | + |
| 168 | +- Use **early returns** to reduce nesting. |
| 169 | +- Style with **CSS modules using Sass syntax** (`.module.scss` files); never write plain |
| 170 | + inline styles or non-module CSS. |
| 171 | +- Use the `classNames` package for conditional class names. |
| 172 | +- Name event handlers with the `handle` prefix: `handleClick`, `handleKeyDown`. |
| 173 | +- Add accessibility attributes on interactive elements: `tabIndex`, `aria-label`, |
| 174 | + `onKeyDown` alongside `onClick`. |
| 175 | + |
| 176 | +### Logging |
| 177 | + |
| 178 | +Use `roarr` scoped loggers — **not** `console.log`: |
| 179 | + |
| 180 | +```ts |
| 181 | +import { createLogger } from "../common/logger.js"; |
| 182 | +const log = createLogger("myModule"); |
| 183 | +log.debug("message", { context }); |
| 184 | +``` |
| 185 | + |
| 186 | +### Error handling across IPC |
| 187 | + |
| 188 | +Serialize errors before sending them over IPC using `serialize-error`: |
| 189 | + |
| 190 | +```ts |
| 191 | +import { serializeError } from "serialize-error"; |
| 192 | +// in server event payload: |
| 193 | +{ error: serializeError(err) } |
| 194 | +``` |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## Testing |
| 199 | + |
| 200 | +- Framework: **Vitest** (raga-web-app, raga-lib) |
| 201 | +- Test files are colocated next to source files as `*.test.ts` or `*.test.tsx` |
| 202 | +- Run all tests: `yarn test` |
| 203 | +- Run tests in watch mode (from web-app package): `yarn workspace @adahiya/raga-web-app test --watch` |
| 204 | + |
| 205 | +--- |
| 206 | + |
| 207 | +## Known Gotchas |
| 208 | + |
| 209 | +- **`skipLibCheck: true` in raga-app** — there are pre-existing third-party type |
| 210 | + conflicts in the Electron package. Do not attempt to resolve them; this is intentional. |
| 211 | +- **ffmpeg must be on PATH** — the audio conversion feature calls the system `ffmpeg` |
| 212 | + binary. Install it separately (e.g., `brew install ffmpeg`). |
| 213 | +- **Discogs API credentials** — genre lookup requires `DISCOGS_CONSUMER_KEY` and |
| 214 | + `DISCOGS_CONSUMER_SECRET` in `packages/raga-app/.env`. See `.env.example` at the |
| 215 | + repo root. |
| 216 | +- **glide-data-grid is canvas-based** — the track table (`TrackTable` component) uses |
| 217 | + `@glideapps/glide-data-grid` which renders to a `<canvas>` element, not DOM rows. |
| 218 | + Do not try to inspect or manipulate individual row DOM nodes. |
| 219 | +- **Node 24 required** — the project uses Node 24 APIs. The GitHub Actions workflow |
| 220 | + currently pins Node 20 (a known discrepancy); CircleCI uses the correct `cimg/node:24.12`. |
| 221 | +- **IPC event queuing** — the preload script queues renderer → server events until the |
| 222 | + `APP_SERVER_READY` signal arrives. If you add a new startup flow, be aware events |
| 223 | + sent before ready will be buffered and replayed. |
0 commit comments