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
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
@ -89,7 +89,7 @@ Checks Gitea Releases API on startup (after 3s) and hourly. Downloads NSIS Setup
### 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

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.
Construite avec **Electron + Vite + TypeScript** en architecture **DDD hexagonale**.
@ -39,7 +39,7 @@ Construite avec **Electron + Vite + TypeScript** en architecture **DDD hexagonal
## 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)
3. Double-cliquer pour lancer l'installation
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
npm run build
# → 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)
# 3. Committer et tagger
@ -133,7 +133,7 @@ git push && git push --tags
# 4. Sur Gitea : Releases → Nouvelle release → tag v1.x.x
# Attacher les 2 fichiers :
# - Minuteur Dragodinde Setup 1.x.x.exe
# - Obsidienne Setup 1.x.x.exe
# - latest.yml
```
@ -236,6 +236,12 @@ src/
- 🛡 **Nettoyage Ctrl+Z listener**`removeEventListener` dans `destroy()` pour éviter les memory leaks
- 🛡 **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
- ⬆ **Migration electron-updater** — vérification sha512 via `latest.yml`, installation NSIS native, restart auto
- 🎨 **Icône Windows native** — migration `icon.png``icon.ico`
@ -303,7 +309,7 @@ src/
- 📝 **Sous-onglets par enclos** (Elevage / Historique bebes)
- 🔧 **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
- 🔧 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
### 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.

View File

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

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Minuteur Dragodinde - Notifications</title>
<title>Obsidienne - Notifications</title>
<style>
*{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}
@ -23,7 +23,7 @@ h1{font-size:1.3rem;margin-bottom:8px}
</head>
<body>
<div class="card">
<h1>Minuteur Dragodinde</h1>
<h1>Obsidienne</h1>
<p class="sub">Notifications mobiles</p>
<div id="loading">

4
package-lock.json generated
View File

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

View File

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

View File

@ -3,7 +3,7 @@
<html class="dark" lang="fr"><head>
<meta charset="utf-8"/>
<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>
<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"/>

View File

@ -3,7 +3,7 @@
<html class="dark" lang="fr"><head>
<meta charset="utf-8"/>
<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>
<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>

View File

@ -14,10 +14,10 @@ import fs from 'fs';
import { autoUpdater } from 'electron-updater';
// ─── NOM DE L'APPLICATION ─────────────────────────────────────────────────────
app.setName('Minuteur Dragodinde');
app.setName('Obsidienne');
// Windows utilise l'AppUserModelId pour le nom affiché dans les notifications
if (process.platform === 'win32') {
app.setAppUserModelId('Minuteur Dragodinde');
app.setAppUserModelId('Obsidienne');
}
// ─── 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);
} else if (!app.isPackaged) {
// 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 ─────────────────────────────────────────────────────────────
@ -59,7 +75,7 @@ function getAppIcon(): Electron.NativeImage {
function createWindow(): void {
mainWindow = new BrowserWindow({
width: 1280, height: 900, minWidth: 960, minHeight: 650,
title: 'Minuteur Dragodinde - Dofus 3',
title: 'Obsidienne - Dofus 3',
icon: getAppIcon(),
backgroundColor: '#0b0b14',
webPreferences: {
@ -85,7 +101,7 @@ function createWindow(): void {
buttons: ['Minimiser', 'Quitter'],
defaultId: 0,
cancelId: 0,
title: 'Minuteur Dragodinde',
title: 'Obsidienne',
message: 'Que souhaites-tu faire ?',
detail: 'Minimiser garde l\'app en arriere-plan.\nLes alarmes continueront de sonner.',
});
@ -127,7 +143,7 @@ function createWindow(): void {
// ─── TRAY ─────────────────────────────────────────────────────────────────────
function createTray(): void {
tray = new Tray(getAppIcon());
tray.setToolTip(`Minuteur Dragodinde v${CURRENT_VERSION}`);
tray.setToolTip(`Obsidienne v${CURRENT_VERSION}`);
rebuildTrayMenu();
tray.on('double-click', () => { mainWindow!.show(); mainWindow!.focus(); });
}
@ -135,7 +151,7 @@ function createTray(): void {
function rebuildTrayMenu(): void {
if (!tray) return;
const items: Electron.MenuItemConstructorOptions[] = [
{ label: `Minuteur Dragodinde v${CURRENT_VERSION}`, enabled: false },
{ label: `Obsidienne v${CURRENT_VERSION}`, enabled: false },
{ type: 'separator' },
{ 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`,
method: 'GET',
headers: {
'User-Agent': `MinuteurDragodinde/${CURRENT_VERSION}`,
'User-Agent': `Obsidienne/${CURRENT_VERSION}`,
'Accept': 'application/json',
},
};

View File

@ -56,7 +56,7 @@ export class App {
<header class="app-header">
<button class="app-hamburger" id="hamburger-btn">&#9776;</button>
<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>
</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();
if (!ntfyTopic) return;
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 ══ */
@ -319,7 +319,7 @@ export class ParametresView {
const version = await api.getVersion?.() ?? 'unknown';
const backup = {
app: 'minuteur-dragodinde',
app: 'obsidienne',
version,
exportedAt: new Date().toISOString(),
data: JSON.parse(raw),
@ -366,7 +366,7 @@ export class ParametresView {
};
// 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)) {
Toast.show('error', 'Le backup contient des données corrompues ou incomplètes.');
return;

View File

@ -57,19 +57,19 @@ describe('Sécurité — Hardening', () => {
describe('Validation backup — format métadonnées', () => {
it('backup valide contient app, version, exportedAt, data', () => {
const backup = {
app: 'minuteur-dragodinde',
app: 'obsidienne',
version: '1.1.6',
exportedAt: new Date().toISOString(),
data: { enclos: [] },
};
expect(backup.app).toBe('minuteur-dragodinde');
expect(backup.app).toBe('obsidienne');
expect(typeof backup.version).toBe('string');
expect(typeof backup.exportedAt).toBe('string');
expect(backup.data).toBeDefined();
});
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é
expect(backup.data === null).toBe(true);
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', () => {
expect(validateEnclosData({ app: 'minuteur-dragodinde', data: null })).toBe(false);
expect(validateEnclosData({ app: 'obsidienne', data: null })).toBe(false);
});
});