# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Règles de collaboration - **Toujours répondre et écrire en français** - **Ne jamais modifier les fichiers de tests sans le signaler explicitement** à l'utilisateur avant de le faire - **Toujours écrire des tests** (unitaires + fonctionnels/régression) lors de toute correction de bug ou ajout de feature — les tests font partie de la livraison, pas une étape optionnelle - **Toujours mettre à jour `docs/algorithmes.md`** quand un algorithme ou une formule de calcul change - **Ne jamais utiliser de taux fixe pour les jauges** — toujours utiliser `gainedIn` / `timeToGain` avec traversée des tiers (erreur historique du monolithe) - **Signaler explicitement** tout changement qui touche aux snapshots du timer (`snapGauges`, `snapStats`, `gaugeRecharges`) car cela peut affecter tous les calculs en cours de session - **Les tests ne tournent pas en WSL** — toujours rappeler à l'utilisateur de lancer `npm test` depuis un terminal Windows ## Project Overview Obsidienne (anciennement "Minuteur Dragodinde") is a Windows desktop app (Electron + Vite + TypeScript) for managing Dragodinde breeding timers in Dofus 3. French-language UI. ## Commands - **Dev**: `npm start` (runs Vite dev server + Electron via `vite-plugin-electron`) - **Build**: `npm run build` (Vite build + electron-builder → NSIS installer in `dist/`) - **Test**: `npm test` (Vitest, run from Windows — native bindings incompatible with WSL) - **Test watch**: `npm run test:watch` - **Build scripts**: `build.bat` (auto-elevates to admin) or `build.ps1` for Windows ## Architecture **DDD hexagonal architecture** with Vite + TypeScript. Four layers: ``` src/ domain/ — Entities, value objects, domain services (pure TS, no deps) application/ — Command handlers, query handlers, CommandBus/QueryBus infrastructure/ — LocalStorageRepository, ElectronNotification, WebAudioAlarm presentation/ — Components, helpers, UIState, entry point (index.ts) ``` ### Domain layer - **`entities/Enclos.ts`** — Enclos entity with `TimerData` (includes `gaugeRecharges: Record`) - **`entities/Dragodinde.ts`** — DD entity with stats, targets, `levelTarget`, `sereniteTarget` - **`services/GaugeCalculator.ts`** — Core pure functions: `gainedIn`, `timeToGain`, `gaugeAfter`, `elapsed`, `computeGaugeState` - **`value-objects/GaugeType.ts`** — `GAUGE_DEFS`, `STAT_DEFS`, `DEFAULT_TARGETS` (mangeoire default = 100, but UI uses 200 when `levelTarget` is null) - **`value-objects/XpTable.ts`** — `xpForLevel`, `levelFromXp` - **`value-objects/Tier.ts`** — `tierNum`, `tierRate` ### Application layer Commands: `StartTimer`, `StopTimer`, `CompleteTimer`, `ResetTimer`, `CreateEnclos`, `DeleteEnclos`, `AddDragodinde`, `RemoveDragodinde`, `UpdateGauge` (toggle + level), `RechargeGauge`, `RegisterAccouplement`, `UpdateSettings`, `ResetStats`, `ReorderEnclos`, `UpdateWorkflow`, `DeleteWorkflow`, `EnclosActions` (clear, rename, reset-timer), `DragodindeActions` (rename, update-stat, seren-target, level-target, reorder) Queries: `GetDashboard`, `GetEnclosDetail`, `GetTimerState`, `GetBreedingOptions`, `GetReapproTree`, `GetInventaire`, `GetSettings`, `GetWorkflows` ### Presentation layer - **`components/App.ts`** — Root component, animation loop (`requestAnimationFrame`), CommandBus/QueryBus wiring - **`components/EnclosView.ts`** — Main timer view: gauge inputs (with `input` listener for real-time updates), DD grid, "Alarme dans" display - **`components/DragodindeCard.ts`** — Per-DD card: stat pills, targets (all with `input` listeners for real-time preview) - **`helpers/gauge-live.ts`** — All live calculation helpers: `computeGaugeLive`, `enclosGaugeCurGl`, `enclosGlobalState`, `calcSerenEtaLive`, `calcLevelEtaLive` ### Key data flows **Timer start** (fresh): snapshots `gaugeLevels` → `snapGauges`, DD stats → `snapStats`, clears `gaugeRecharges` **Timer resume** (from pause): accumulates `pausedMs += now - pausedAt`, preserves snapshots **Gauge recharge** (during timer): pushes `{ atSec, level }` to `gaugeRecharges[gid]`, updates `gaugeLevels[gid]` **Live display**: `requestAnimationFrame` loop → `enclosGlobalState(enc)` → per-DD `computeGaugeLive` → DOM updates ### Timer calculation core — `computeGaugeState` The central calculation function. Handles: - Multiple gauge recharges during a session (segments between each recharge) - Freeze at absolute stat cap (sérénité ±5000, end/mat/amour 20000, niveau 200) - Returns `{ gained, curGl, effectiveEl }` **Pre-start preview**: `enclosGlobalState` computes even when timer not started (`started = false`), using current `gaugeLevels` as `startGl` and `el = 0`. "Alarme dans" updates in real-time as the user types gauge values and DD targets. **XP gauge (mangeoire)**: when the target is unreachable in a single gauge drain, countdown shows drain time instead of `∞`. Formula: `timeToGain(curGl, Math.min(xpRestante, curGl))` — consistent with "Vide en" display. **Default level target**: when `dd.levelTarget === null`, the XP gauge defaults to targeting level 200. ### IPC channels Main→Renderer: `app-version`, `play-alarm-sound`, `update-available`, `update-downloading`, `update-progress`, `update-ready`, `update-error` Renderer→Main: `trigger-alarm`, `show-notification`, `send-ntfy`, `focus-window`, `install-update`, `get-version` ### Auto-update flow Checks Gitea Releases API on startup (after 3s) and hourly. Downloads NSIS Setup .exe to temp, launches with `/S` (silent), then quits app. ### Dev vs packaged mode When `!app.isPackaged`, userData is stored in `Obsidienne-DEV` (isolated from installed app) and a DEV badge is injected into the UI. On first packaged launch, data is auto-migrated from the old `Minuteur Dragodinde` folder. ## Tests ``` tests/ unit/domain/ — GaugeCalculator, Enclos, Dragodinde, XpTable, etc. unit/application/ — commands.test.ts, queries.test.ts, CommandBus.test.ts unit/infrastructure/ functional/ — breeding-workflow, enclos-management, timer-workflow regression/ — gauge-tier-calculation, gauge-recharge, xp-timer-display, etc. ``` Vitest with path aliases (`@domain`, `@application`, `@infrastructure`, `@presentation`). Run with `npm test` from Windows. ## Key conventions - All UI text is in French - TypeScript strict mode — no `any` unless unavoidable - Immutable domain entities (functions return new objects) - Commands mutate shared `state` object directly (no Redux-style reducers) - `repo.save(state)` called after every state mutation - CSS uses custom properties in `:root` (dark purple/gaming aesthetic), component styles in `src/presentation/styles/`