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

6.7 KiB
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.tsGAUGE_DEFS, STAT_DEFS, DEFAULT_TARGETS (mangeoire default = 100, but UI uses 200 when levelTarget is null)
  • value-objects/XpTable.tsxpForLevel, levelFromXp
  • value-objects/Tier.tstierNum, 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 gaugeLevelssnapGauges, 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/