rename: Minuteur Dragodinde → Obsidienne

- Renommage complet dans package.json, main.ts, UI, tray, notifications
- GUID NSIS fixe pour mise à jour propre (pas de doublon d'installation)
- Migration automatique des données depuis %APPDATA%\Minuteur Dragodinde\
- Rétrocompatibilité import : backups 'minuteur-dragodinde' toujours acceptés
- Mise à jour README changelog, CLAUDE.md, docs, maquettes, page ntfy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
POL Mickaël 2026-04-06 07:34:07 +02:00
parent 8af626dd66
commit 0c3b5e27a7
14 changed files with 60 additions and 37 deletions

View File

@ -14,7 +14,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
Minuteur Dragodinde is a Windows desktop app (Electron + Vite + TypeScript) for managing Dragodinde breeding timers in Dofus 3. French-language UI. Obsidienne (anciennement "Minuteur Dragodinde") is a Windows desktop app (Electron + Vite + TypeScript) for managing Dragodinde breeding timers in Dofus 3. French-language UI.
## Commands ## Commands
@ -89,7 +89,7 @@ Checks Gitea Releases API on startup (after 3s) and hourly. Downloads NSIS Setup
### Dev vs packaged mode ### Dev vs packaged mode
When `!app.isPackaged`, userData is stored in `MinuteurDragodinde-DEV` (isolated from installed app) and a DEV badge is injected into the UI. 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

View File

@ -1,4 +1,4 @@
# ⚔ Obsidienne — Minuteur Dragodinde (Dofus 3) # ⚔ Obsidienne — Élevage Dragodinde (Dofus 3)
Application desktop Windows de gestion d'élevage de Dragodindes pour Dofus 3. Application desktop Windows de gestion d'élevage de Dragodindes pour Dofus 3.
Construite avec **Electron + Vite + TypeScript** en architecture **DDD hexagonale**. Construite avec **Electron + Vite + TypeScript** en architecture **DDD hexagonale**.
@ -39,7 +39,7 @@ Construite avec **Electron + Vite + TypeScript** en architecture **DDD hexagonal
## Installation (utilisateurs) ## Installation (utilisateurs)
1. Télécharger `Minuteur Dragodinde Setup x.x.x.exe` depuis la section [Releases](https://gitea.mickael-pol.fr/mickael/dd-timer/releases) 1. Télécharger `Obsidienne Setup x.x.x.exe` depuis la section [Releases](https://gitea.mickael-pol.fr/mickael/dd-timer/releases)
2. **Clic droit → Propriétés → cocher "Débloquer" → OK** (important, une seule fois) 2. **Clic droit → Propriétés → cocher "Débloquer" → OK** (important, une seule fois)
3. Double-cliquer pour lancer l'installation 3. Double-cliquer pour lancer l'installation
4. L'app apparaît dans le menu Démarrer et sur le Bureau 4. L'app apparaît dans le menu Démarrer et sur le Bureau
@ -122,7 +122,7 @@ L'application utilise **electron-updater** avec un fichier `latest.yml` pour la
# 2. Build l'application # 2. Build l'application
npm run build npm run build
# → Génère dans dist/ : # → Génère dans dist/ :
# - Minuteur Dragodinde Setup 1.x.x.exe # - Obsidienne Setup 1.x.x.exe
# - latest.yml (version + sha512, requis par electron-updater) # - latest.yml (version + sha512, requis par electron-updater)
# 3. Committer et tagger # 3. Committer et tagger
@ -133,7 +133,7 @@ git push && git push --tags
# 4. Sur Gitea : Releases → Nouvelle release → tag v1.x.x # 4. Sur Gitea : Releases → Nouvelle release → tag v1.x.x
# Attacher les 2 fichiers : # Attacher les 2 fichiers :
# - Minuteur Dragodinde Setup 1.x.x.exe # - Obsidienne Setup 1.x.x.exe
# - latest.yml # - latest.yml
``` ```
@ -236,6 +236,12 @@ src/
- 🛡 **Nettoyage Ctrl+Z listener**`removeEventListener` dans `destroy()` pour éviter les memory leaks - 🛡 **Nettoyage Ctrl+Z listener**`removeEventListener` dans `destroy()` pour éviter les memory leaks
- 🛡 **Toast stale container** — protection `isConnected` contre les conteneurs DOM détachés - 🛡 **Toast stale container** — protection `isConnected` contre les conteneurs DOM détachés
#### Renommage
- 🏷 **Renommage "Minuteur Dragodinde" → "Obsidienne"** — nouveau nom d'application, raccourcis, titre, tray, notifications
- 🔄 **Migration automatique des données** — copie transparente du fichier de sauvegarde depuis l'ancien dossier `%APPDATA%\Minuteur Dragodinde\` au premier lancement
- 🔄 **GUID NSIS fixe** — l'installeur reconnaît l'ancienne version et la remplace proprement (pas de doublon)
- 🔄 **Rétrocompatibilité import** — les backups exportés avec `app: 'minuteur-dragodinde'` restent importables
#### Technique #### Technique
- ⬆ **Migration electron-updater** — vérification sha512 via `latest.yml`, installation NSIS native, restart auto - ⬆ **Migration electron-updater** — vérification sha512 via `latest.yml`, installation NSIS native, restart auto
- 🎨 **Icône Windows native** — migration `icon.png``icon.ico` - 🎨 **Icône Windows native** — migration `icon.png``icon.ico`
@ -303,7 +309,7 @@ src/
- 📝 **Sous-onglets par enclos** (Elevage / Historique bebes) - 📝 **Sous-onglets par enclos** (Elevage / Historique bebes)
- 🔧 **Mode DEV** — Donnees isolees et badge DEV visible quand lance avec `npm start` - 🔧 **Mode DEV** — Donnees isolees et badge DEV visible quand lance avec `npm start`
- ⬆ **Mise a jour automatique** via Gitea Releases avec banniere de progression dans l'interface - ⬆ **Mise a jour automatique** via Gitea Releases avec banniere de progression dans l'interface
- 🔧 Correction de l'identifiant applicatif (`fr.mickael-pol.minuteur-dragodinde`) - 🔧 Correction de l'identifiant applicatif (`fr.mickael-pol.obsidienne`)
- 🔧 Masquage des spinners natifs sur les champs numeriques - 🔧 Masquage des spinners natifs sur les champs numeriques
### v1.0.0 ### v1.0.0

View File

@ -1,4 +1,4 @@
# Algorithmes de calcul — Minuteur Dragodinde # Algorithmes de calcul — Obsidienne
Ce document décrit tous les algorithmes utilisés dans l'application, expliqués simplement. Ce document décrit tous les algorithmes utilisés dans l'application, expliqués simplement.

View File

@ -1,4 +1,4 @@
# Fonctionnalités par écran — Minuteur Dragodinde # Fonctionnalités par écran — Obsidienne
--- ---

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Minuteur Dragodinde - Notifications</title> <title>Obsidienne - Notifications</title>
<style> <style>
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0b0b14;color:#dddaf8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px} body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0b0b14;color:#dddaf8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
@ -23,7 +23,7 @@ h1{font-size:1.3rem;margin-bottom:8px}
</head> </head>
<body> <body>
<div class="card"> <div class="card">
<h1>Minuteur Dragodinde</h1> <h1>Obsidienne</h1>
<p class="sub">Notifications mobiles</p> <p class="sub">Notifications mobiles</p>
<div id="loading"> <div id="loading">

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "minuteur-dragodinde", "name": "obsidienne",
"version": "1.1.6", "version": "1.1.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "minuteur-dragodinde", "name": "obsidienne",
"version": "1.1.6", "version": "1.1.6",
"dependencies": { "dependencies": {
"electron-updater": "^6.8.3" "electron-updater": "^6.8.3"

View File

@ -1,7 +1,7 @@
{ {
"name": "minuteur-dragodinde", "name": "obsidienne",
"version": "1.1.6", "version": "1.1.6",
"description": "Minuteur elevage Dragodinde Dofus 3", "description": "Obsidienne — Minuteur d'élevage Dragodinde pour Dofus 3",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"author": "Mickael", "author": "Mickael",
"scripts": { "scripts": {
@ -14,12 +14,12 @@
"test:e2e": "npx playwright test" "test:e2e": "npx playwright test"
}, },
"build": { "build": {
"appId": "fr.mickael-pol.minuteur-dragodinde", "appId": "fr.mickael-pol.obsidienne",
"publish": { "publish": {
"provider": "generic", "provider": "generic",
"url": "https://gitea.mickael-pol.fr/mickael/dd-timer/releases/download/latest" "url": "https://gitea.mickael-pol.fr/mickael/dd-timer/releases/download/latest"
}, },
"productName": "Minuteur Dragodinde", "productName": "Obsidienne",
"directories": { "directories": {
"output": "dist" "output": "dist"
}, },
@ -48,7 +48,8 @@
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "Minuteur Dragodinde", "shortcutName": "Obsidienne",
"guid": "3b4a21ac-02a8-4525-a48b-988079fc75d4",
"deleteAppDataOnUninstall": false, "deleteAppDataOnUninstall": false,
"runAfterFinish": true "runAfterFinish": true
} }
@ -71,7 +72,7 @@
"type": "git", "type": "git",
"url": "https://gitea.mickael-pol.fr/mickael/dd-timer.git" "url": "https://gitea.mickael-pol.fr/mickael/dd-timer.git"
}, },
"productName": "Minuteur Dragodinde", "productName": "Obsidienne",
"dependencies": { "dependencies": {
"electron-updater": "^6.8.3" "electron-updater": "^6.8.3"
} }

View File

@ -3,7 +3,7 @@
<html class="dark" lang="fr"><head> <html class="dark" lang="fr"><head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Minuteur Dragodinde - L'Archive d'Obsidienne</title> <title>Obsidienne - Dashboard</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,container-queries"></script> <script src="https://cdn.tailwindcss.com?plugins=forms,typography,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Cinzel:wght@400;700&amp;display=swap" rel="stylesheet"/> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Cinzel:wght@400;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet"/> <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet"/>

View File

@ -3,7 +3,7 @@
<html class="dark" lang="fr"><head> <html class="dark" lang="fr"><head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Minuteur Dragodinde - Enclos 1</title> <title>Obsidienne - Enclos 1</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,container-queries"></script> <script src="https://cdn.tailwindcss.com?plugins=forms,typography,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Outfit:wght@400;600;700&amp;family=Material+Icons+Round&amp;display=swap" rel="stylesheet"/> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Outfit:wght@400;600;700&amp;family=Material+Icons+Round&amp;display=swap" rel="stylesheet"/>
<script> <script>

View File

@ -14,10 +14,10 @@ import fs from 'fs';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
// ─── NOM DE L'APPLICATION ───────────────────────────────────────────────────── // ─── NOM DE L'APPLICATION ─────────────────────────────────────────────────────
app.setName('Minuteur Dragodinde'); app.setName('Obsidienne');
// Windows utilise l'AppUserModelId pour le nom affiché dans les notifications // Windows utilise l'AppUserModelId pour le nom affiché dans les notifications
if (process.platform === 'win32') { if (process.platform === 'win32') {
app.setAppUserModelId('Minuteur Dragodinde'); app.setAppUserModelId('Obsidienne');
} }
// ─── MODE DEV / E2E ────────────────────────────<E29480><E29480>───────────────────────────── // ─── MODE DEV / E2E ────────────────────────────<E29480><E29480>─────────────────────────────
@ -26,7 +26,23 @@ if (process.env.ELECTRON_USER_DATA_DIR) {
app.setPath('userData', process.env.ELECTRON_USER_DATA_DIR); app.setPath('userData', process.env.ELECTRON_USER_DATA_DIR);
} else if (!app.isPackaged) { } else if (!app.isPackaged) {
// En dev (npm start), les données sont isolées de l'app installée // En dev (npm start), les données sont isolées de l'app installée
app.setPath('userData', path.join(app.getPath('appData'), 'MinuteurDragodinde-DEV')); app.setPath('userData', path.join(app.getPath('appData'), 'Obsidienne-DEV'));
}
// ─── MIGRATION DONNÉES (ancien nom → Obsidienne) ────────────────────────────
// Les utilisateurs de "Minuteur Dragodinde" conservent leurs données après le renommage
if (app.isPackaged) {
const oldDataFile = path.join(app.getPath('appData'), 'Minuteur Dragodinde', 'dd-timer-data.json');
const newDataFile = path.join(app.getPath('userData'), 'dd-timer-data.json');
if (!fs.existsSync(newDataFile) && fs.existsSync(oldDataFile)) {
try {
fs.mkdirSync(path.dirname(newDataFile), { recursive: true });
fs.copyFileSync(oldDataFile, newDataFile);
console.log('Migration données: Minuteur Dragodinde → Obsidienne OK');
} catch (e: unknown) {
console.error('Migration données échouée:', (e as Error).message);
}
}
} }
// ─── CONFIG GITEA ───────────────────────────────────────────────────────────── // ─── CONFIG GITEA ─────────────────────────────────────────────────────────────
@ -59,7 +75,7 @@ function getAppIcon(): Electron.NativeImage {
function createWindow(): void { function createWindow(): void {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1280, height: 900, minWidth: 960, minHeight: 650, width: 1280, height: 900, minWidth: 960, minHeight: 650,
title: 'Minuteur Dragodinde - Dofus 3', title: 'Obsidienne - Dofus 3',
icon: getAppIcon(), icon: getAppIcon(),
backgroundColor: '#0b0b14', backgroundColor: '#0b0b14',
webPreferences: { webPreferences: {
@ -85,7 +101,7 @@ function createWindow(): void {
buttons: ['Minimiser', 'Quitter'], buttons: ['Minimiser', 'Quitter'],
defaultId: 0, defaultId: 0,
cancelId: 0, cancelId: 0,
title: 'Minuteur Dragodinde', title: 'Obsidienne',
message: 'Que souhaites-tu faire ?', message: 'Que souhaites-tu faire ?',
detail: 'Minimiser garde l\'app en arriere-plan.\nLes alarmes continueront de sonner.', detail: 'Minimiser garde l\'app en arriere-plan.\nLes alarmes continueront de sonner.',
}); });
@ -127,7 +143,7 @@ function createWindow(): void {
// ─── TRAY ───────────────────────────────────────────────────────────────────── // ─── TRAY ─────────────────────────────────────────────────────────────────────
function createTray(): void { function createTray(): void {
tray = new Tray(getAppIcon()); tray = new Tray(getAppIcon());
tray.setToolTip(`Minuteur Dragodinde v${CURRENT_VERSION}`); tray.setToolTip(`Obsidienne v${CURRENT_VERSION}`);
rebuildTrayMenu(); rebuildTrayMenu();
tray.on('double-click', () => { mainWindow!.show(); mainWindow!.focus(); }); tray.on('double-click', () => { mainWindow!.show(); mainWindow!.focus(); });
} }
@ -135,7 +151,7 @@ function createTray(): void {
function rebuildTrayMenu(): void { function rebuildTrayMenu(): void {
if (!tray) return; if (!tray) return;
const items: Electron.MenuItemConstructorOptions[] = [ const items: Electron.MenuItemConstructorOptions[] = [
{ label: `Minuteur Dragodinde v${CURRENT_VERSION}`, enabled: false }, { label: `Obsidienne v${CURRENT_VERSION}`, enabled: false },
{ type: 'separator' }, { type: 'separator' },
{ label: 'Ouvrir', click: () => { mainWindow!.show(); mainWindow!.focus(); } }, { label: 'Ouvrir', click: () => { mainWindow!.show(); mainWindow!.focus(); } },
]; ];
@ -361,7 +377,7 @@ function checkForUpdates(silent = false): void {
path: `/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases?limit=1`, path: `/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases?limit=1`,
method: 'GET', method: 'GET',
headers: { headers: {
'User-Agent': `MinuteurDragodinde/${CURRENT_VERSION}`, 'User-Agent': `Obsidienne/${CURRENT_VERSION}`,
'Accept': 'application/json', 'Accept': 'application/json',
}, },
}; };

View File

@ -56,7 +56,7 @@ export class App {
<header class="app-header"> <header class="app-header">
<button class="app-hamburger" id="hamburger-btn">&#9776;</button> <button class="app-hamburger" id="hamburger-btn">&#9776;</button>
<div class="app-header-text"> <div class="app-header-text">
<h1 class="app-title"><span class="app-title-icon">&#x2694;</span> Minuteur Dragodinde</h1> <h1 class="app-title"><span class="app-title-icon">&#x2694;</span> Obsidienne</h1>
<p class="app-subtitle">Dofus 3 &middot; Gestion multi-enclos en temps r&eacute;el</p> <p class="app-subtitle">Dofus 3 &middot; Gestion multi-enclos en temps r&eacute;el</p>
</div> </div>
<div class="app-hamburger" style="visibility:hidden;pointer-events:none;" aria-hidden="true"></div> <div class="app-hamburger" style="visibility:hidden;pointer-events:none;" aria-hidden="true"></div>

View File

@ -301,7 +301,7 @@ export class ParametresView {
const { ntfyTopic } = this.getSettings(); const { ntfyTopic } = this.getSettings();
if (!ntfyTopic) return; if (!ntfyTopic) return;
const url = `${NTFY_BASE}/${ntfyTopic}`; const url = `${NTFY_BASE}/${ntfyTopic}`;
(window as any).electronAPI?.sendNtfy?.(url, 'Test alarme', 'Ceci est un test de la notification mobile Minuteur Dragodinde !'); (window as any).electronAPI?.sendNtfy?.(url, 'Test alarme', 'Ceci est un test de la notification mobile Obsidienne !');
} }
/* ══ Backup / Restore ══ */ /* ══ Backup / Restore ══ */
@ -319,7 +319,7 @@ export class ParametresView {
const version = await api.getVersion?.() ?? 'unknown'; const version = await api.getVersion?.() ?? 'unknown';
const backup = { const backup = {
app: 'minuteur-dragodinde', app: 'obsidienne',
version, version,
exportedAt: new Date().toISOString(), exportedAt: new Date().toISOString(),
data: JSON.parse(raw), data: JSON.parse(raw),
@ -366,7 +366,7 @@ export class ParametresView {
}; };
// Validation du format backup // Validation du format backup
if (parsed.app === 'minuteur-dragodinde' && parsed.data && typeof parsed.data === 'object' && parsed.data !== null) { if ((parsed.app === 'obsidienne' || parsed.app === 'minuteur-dragodinde') && parsed.data && typeof parsed.data === 'object' && parsed.data !== null) {
if (!validateEnclosData(parsed.data)) { if (!validateEnclosData(parsed.data)) {
Toast.show('error', 'Le backup contient des données corrompues ou incomplètes.'); Toast.show('error', 'Le backup contient des données corrompues ou incomplètes.');
return; return;

View File

@ -57,19 +57,19 @@ describe('Sécurité — Hardening', () => {
describe('Validation backup — format métadonnées', () => { describe('Validation backup — format métadonnées', () => {
it('backup valide contient app, version, exportedAt, data', () => { it('backup valide contient app, version, exportedAt, data', () => {
const backup = { const backup = {
app: 'minuteur-dragodinde', app: 'obsidienne',
version: '1.1.6', version: '1.1.6',
exportedAt: new Date().toISOString(), exportedAt: new Date().toISOString(),
data: { enclos: [] }, data: { enclos: [] },
}; };
expect(backup.app).toBe('minuteur-dragodinde'); expect(backup.app).toBe('obsidienne');
expect(typeof backup.version).toBe('string'); expect(typeof backup.version).toBe('string');
expect(typeof backup.exportedAt).toBe('string'); expect(typeof backup.exportedAt).toBe('string');
expect(backup.data).toBeDefined(); expect(backup.data).toBeDefined();
}); });
it('data: null est détecté comme invalide', () => { it('data: null est détecté comme invalide', () => {
const backup = { app: 'minuteur-dragodinde', data: null }; const backup = { app: 'obsidienne', data: null };
// typeof null === 'object' mais data === null doit être rejeté // typeof null === 'object' mais data === null doit être rejeté
expect(backup.data === null).toBe(true); expect(backup.data === null).toBe(true);
expect(!!(backup.data && typeof backup.data === 'object' && backup.data !== null)).toBe(false); expect(!!(backup.data && typeof backup.data === 'object' && backup.data !== null)).toBe(false);

View File

@ -112,6 +112,6 @@ describe('Validation import — validateEnclosData', () => {
}); });
it('rejette un objet backup avec data: null', () => { it('rejette un objet backup avec data: null', () => {
expect(validateEnclosData({ app: 'minuteur-dragodinde', data: null })).toBe(false); expect(validateEnclosData({ app: 'obsidienne', data: null })).toBe(false);
}); });
}); });