dd-timer/CLAUDE.md
POL Mickaël 3e485fd09b chore: normalise fins de ligne CRLF → LF dans tout le repo
Applique .gitattributes sur tous les fichiers existants.
Élimine les différences fantômes entre WSL et Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 08:55:10 +02:00

115 lines
6.7 KiB
Markdown
Executable File

# 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 `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<string, GaugeRecharge[]>`)
- **`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/`