= ({ title }) => {
+ return {title}
;
+ };
+
+ export default MyComponent;
+ ```
+
+2. Import and use it:
+
+ ```tsx
+ import MyComponent from './components/MyComponent';
+
+ // In your JSX:
+ ;
+ ```
+
+### Adding a Custom Hook
+
+1. Create a new file in `src/hooks/`:
+
+ ```tsx
+ // src/hooks/useMyHook.ts
+ import { useState, useEffect } from 'react';
+
+ export function useMyHook(initialValue: string) {
+ const [value, setValue] = useState(initialValue);
+
+ useEffect(() => {
+ console.log('Value changed:', value);
+ }, [value]);
+
+ return [value, setValue] as const;
+ }
+ ```
+
+2. Use it in a component:
+ ```tsx
+ const [value, setValue] = useMyHook('initial');
+ ```
+
+### Writing Tests
+
+Create a test file next to your code:
+
+```tsx
+// src/utils/myUtil.test.ts
+import { describe, it, expect } from 'vitest';
+import { myFunction } from './myUtil';
+
+describe('myFunction', () => {
+ it('should return correct result', () => {
+ expect(myFunction(1, 2)).toBe(3);
+ });
+});
+```
+
+## VSCode Setup
+
+If you're using VSCode, install the recommended extensions when prompted. They provide:
+
+- ✅ Automatic code formatting on save
+- ✅ Linting error highlights
+- ✅ TypeScript intellisense
+- ✅ Tailwind CSS autocomplete
+- ✅ Test runner integration
+
+## Troubleshooting
+
+### Port Already in Use
+
+If port 5173 is already in use:
+
+```bash
+# Edit vite.config.ts and change the port
+server: {
+ port: 3000 // or any other port
+}
+```
+
+### Dependencies Issues
+
+Clear everything and reinstall:
+
+```bash
+npm run clean:all
+npm install
+```
+
+### Tests Failing
+
+Make sure you're running the latest version:
+
+```bash
+git pull
+npm install
+npm test
+```
+
+## Next Steps
+
+- 📖 Read the full [README.md](./README.md) for detailed documentation
+- 🤝 Check [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution guidelines
+- 📋 Review [CHANGELOG.md](./CHANGELOG.md) for recent changes
+- 🐛 Report bugs or request features on [GitHub Issues](https://github.com/phuetz/ChessDatabase/issues)
+
+## Need Help?
+
+- Check existing [GitHub Issues](https://github.com/phuetz/ChessDatabase/issues)
+- Read the [Contributing Guide](./CONTRIBUTING.md)
+- Review the code - it's well-documented with JSDoc comments!
+
+## Keyboard Shortcuts
+
+Press `?` in the application to see all available keyboard shortcuts!
+
+---
+
+Happy coding! ♟️ If you find this project useful, consider giving it a ⭐ on GitHub!
diff --git a/README.md b/README.md
index 5162529..7c384b1 100644
--- a/README.md
+++ b/README.md
@@ -32,36 +32,52 @@
### 🎮 Interface Principale
+
-*Interface principale avec navigation intuitive entre 13 vues différentes*
+
+_Interface principale avec navigation intuitive entre 13 vues différentes_
### 🔍 Analyseur de Positions avec Stockfish 16
+
-*Analyse en profondeur avec visualisation des menaces et suggestions de coups*
+
+_Analyse en profondeur avec visualisation des menaces et suggestions de coups_
### 📚 Encyclopédie d'Ouvertures
+
-*Base de données complète d'ouvertures avec codes ECO et statistiques*
+
+_Base de données complète d'ouvertures avec codes ECO et statistiques_
### 👥 Base de Données de Joueurs
+
-*Gestion complète de joueurs avec statistiques, classements et historiques*
+
+_Gestion complète de joueurs avec statistiques, classements et historiques_
### 🎯 Entraînement Tactique
+
-*4 modes de puzzles pour améliorer vos compétences tactiques*
+
+_4 modes de puzzles pour améliorer vos compétences tactiques_
### 🎨 9 Thèmes d'Échiquier
+
-*Classic, Blue, Green, Wood, Brown, Purple, Pink, Marble, Metal*
+
+_Classic, Blue, Green, Wood, Brown, Purple, Pink, Marble, Metal_
### 🌓 Mode Sombre
+
-*Interface élégante avec support du mode sombre et système*
+
+_Interface élégante avec support du mode sombre et système_
### 🏆 Système de Tournois
+
-*Organisation de tournois Round-Robin avec classements en temps réel*
+
+_Organisation de tournois Round-Robin avec classements en temps réel_
@@ -69,6 +85,7 @@
## 📑 Table des Matières
+- [🆕 Nouveautés v2.1](#-nouveautés-v21)
- [✨ Fonctionnalités](#-fonctionnalités)
- [🎯 Fonctionnalités Principales](#-fonctionnalités-principales)
- [📊 Analyse Avancée](#-analyse-avancée)
@@ -90,11 +107,104 @@
---
+## 🆕 Nouveautés v2.1
+
+### 🛠️ Qualité de Code & DevEx
+
+#### ✅ Tests Automatisés
+
+- **Vitest** pour les tests unitaires ultra-rapides
+- **React Testing Library** pour les tests de composants
+- **Suite de tests complète** :
+ - ✓ Tests pour le calcul ELO
+ - ✓ Tests pour l'évaluation matérielle
+ - ✓ Tests pour les pièces capturées
+ - ✓ Tests pour le système de tournois
+- **Coverage reporting** avec rapports HTML détaillés
+
+#### 🎨 Formatage & Qualité
+
+- **Prettier** pour un code formaté uniformément
+- **Husky + lint-staged** pour des pre-commit hooks
+- **ESLint** avec configuration stricte
+- Formatage automatique à chaque commit
+
+#### 🔄 CI/CD
+
+- **GitHub Actions** workflow complet :
+ - ✓ Linting automatique
+ - ✓ Tests unitaires et E2E
+ - ✓ Vérification TypeScript
+ - ✓ Audits de sécurité
+ - ✓ Build de production
+- **Codecov** pour le suivi de la couverture de code
+
+### 📦 Nouvelles Fonctionnalités
+
+#### 📤 Export Avancé
+
+- **Export JSON** pour backup et analyse
+- **Export CSV** pour Excel/Google Sheets
+- **Export Excel (TSV)** avec encodage UTF-8
+- **Import CSV/JSON** pour restaurer des données
+- Gestion des caractères spéciaux et échappement
+
+#### ♟️ Gestion FEN Complète
+
+- **Import/Export FEN** avec validation
+- **Copie vers le presse-papiers**
+- **Chargement depuis fichier**
+- **Comparaison de positions**
+- **Descriptions automatiques** de positions
+- **Générateur de positions aléatoires**
+- Utilitaires pour FEN simplifiées
+
+#### 🧩 Nouveaux Composants
+
+- **ErrorBoundary** global pour une gestion gracieuse des erreurs
+- **Skeleton Components** pour de meilleures expériences de chargement
+- **KeyboardShortcutsModal** pour afficher les raccourcis clavier
+
+#### 🔧 Hooks Personnalisés
+
+- `useKeyboardShortcuts` - Gestion des raccourcis clavier
+- `useLocalStorage` - Synchronisation avec localStorage
+- `useDebounce` - Optimisation des performances
+- `useMediaQuery` - Responsive design
+- `useIsMobile`, `useIsTablet`, `useIsDesktop` - Breakpoints
+
+### 📚 Documentation Enrichie
+
+#### 📝 Nouveaux Fichiers
+
+- **CONTRIBUTING.md** - Guide complet de contribution
+- **CHANGELOG.md** - Historique des versions
+- **constants.ts** - Configuration centralisée
+- Commentaires **JSDoc** pour toutes les fonctions
+
+#### 📖 Ressources Développeurs
+
+- Templates pour bug reports et feature requests
+- Conventions de nommage et standards de code
+- Exemples de tests unitaires et de composants
+- Guide de commit messages (Conventional Commits)
+
+### 🚀 Améliorations Techniques
+
+- **TypeScript strict mode** amélioré
+- **Validation renforcée** pour FEN et PGN
+- **Messages d'erreur** user-friendly
+- **Gestion d'erreurs** améliorée
+- **Code organization** optimisée avec extraction de hooks
+
+---
+
## ✨ Fonctionnalités
### 🎯 Fonctionnalités Principales
#### 📚 Base de Données de Parties de Maîtres
+
- **Bibliothèque complète** de parties célèbres annotées
- **Import multi-sources** :
- Fichiers PGN locaux (drag & drop)
@@ -110,6 +220,7 @@
- Filtrage en temps réel
#### 🔍 Encyclopédie d'Ouvertures
+
- Base de données complète avec **codes ECO**
- **Variations principales** documentées
- **Statistiques détaillées** :
@@ -120,6 +231,7 @@
- Navigation dans les variantes
#### 👥 Base de Données de Joueurs Professionnelle
+
- **CRUD complet** (Create, Read, Update, Delete)
- **Informations détaillées** :
- Données personnelles (nom, prénom, date de naissance)
@@ -142,6 +254,7 @@
### 📊 Analyse Avancée
#### 🧠 Moteur Stockfish 16 NNUE
+
- **Analyse ultra-performante** avec Web Worker dédié
- **Configuration intelligente** :
- Profondeur adaptative (10-18 basée sur CPU)
@@ -161,6 +274,7 @@
- Indicateurs de menaces
#### 📈 Statistiques de Parties
+
- **Graphiques interactifs** (Chart.js) :
- Distribution des ouvertures
- Taux de victoire par ouverture
@@ -175,6 +289,7 @@
- Suggestions d'amélioration
#### 🎯 Reconnaissance de Patterns
+
- Détection automatique des **patterns classiques** :
- Mat du couloir
- Mat étouffé
@@ -191,6 +306,7 @@
#### 🧩 4 Modes de Puzzles Tactiques
**1. 🎯 Puzzle Trainer**
+
- Base de données de puzzles par thème
- Rating des puzzles (débutant à maître)
- Validation automatique des solutions
@@ -198,23 +314,27 @@
- Feedback instantané
**2. 📅 Daily Puzzle**
+
- Puzzle quotidien unique
- **Streak tracking** (série de jours consécutifs)
- Notifications automatiques
- Historique des puzzles résolus
**3. ⚡ Puzzle Rush**
+
- Mode **contre-la-montre**
- Score basé sur vitesse et précision
- Classement mondial
- Statistiques de performance
**4. ⚔️ Puzzle Battle**
+
- Mode **compétitif** contre d'autres joueurs
- Système de ranking ELO
- Matchmaking équilibré
#### 📚 Mode Apprentissage
+
- **Leçons structurées** par thème :
- Ouvertures fondamentales
- Tactiques de milieu de partie
@@ -225,6 +345,7 @@
- Solutions détaillées avec explications
#### 🎥 Bibliothèque Vidéo
+
- Collection de **vidéos éducatives** :
- Intégration YouTube
- Catégories organisées (Ouvertures, Milieu, Finales)
@@ -232,6 +353,7 @@
- Ressources de grands maîtres
#### 🏆 Système de Succès (Achievements)
+
- **Tracking automatique** :
- Puzzles résolus
- Parties jouées
@@ -245,6 +367,7 @@
### 🎮 Modes de Jeu
#### 🤖 Jouer contre l'Ordinateur
+
- **10 niveaux de difficulté** (profondeur 1-20)
- **Sélection de position de départ** :
- Partie standard
@@ -265,6 +388,7 @@
- Graphiques de performance
#### 🏆 Système de Tournois
+
- **Création de tournois personnalisés**
- **Format Round-Robin** automatique
- Sélection de joueurs depuis la base de données
@@ -277,11 +401,13 @@
- Export des résultats
#### 👀 Mode Spectateur
+
- Visualisation de parties en cours
- Streaming de parties live
- Commentaires en temps réel
#### 🎖️ Leaderboards
+
- Classements mondiaux :
- Meilleurs scores puzzle
- Top joueurs du mode ordinateur
@@ -320,6 +446,7 @@
### 🎨 Personnalisation
#### 🎨 9 Thèmes d'Échiquier
+
1. **Classic** - Marron et beige traditionnel
2. **Blue** - Bleu océan moderne
3. **Green** - Vert forêt apaisant
@@ -333,6 +460,7 @@
Persistence des préférences en localStorage.
#### 🌓 3 Modes de Thème UI
+
- **Light** - Interface claire
- **Dark** - Mode sombre élégant
- **System** - Suit les préférences système
@@ -341,6 +469,7 @@ Persistence des préférences en localStorage.
- Transitions fluides
#### 🔔 Centre de Notifications
+
- **Système complet** avec React Toastify :
- 4 types : Info, Success, Error, Warning
- Durée configurable
@@ -487,6 +616,7 @@ ChessDatabase/
### 🏛️ Patterns Architecturaux
#### State Management (Zustand)
+
- **Store centralisé** avec `subscribeWithSelector`
- **Persistence localStorage** pour :
- Préférences utilisateur (thème, langue)
@@ -501,6 +631,7 @@ ChessDatabase/
- Données de tournois
#### Server State (React Query)
+
- Cache optimisé (5 min staleTime)
- Retry automatique (2 tentatives)
- Gestion d'erreurs API (429, 404)
@@ -508,6 +639,7 @@ ChessDatabase/
- Rate limiting avec `p-queue`
#### Component Architecture
+
- **Lazy Loading** pour performances :
- `React.lazy` pour ChessAnalyzer
- `React.lazy` pour ChessOpenings
@@ -522,64 +654,64 @@ ChessDatabase/
### 🎯 Core Stack
-| Technologie | Version | Usage |
-|------------|---------|-------|
-|  | 18.3.1 | Framework UI |
-|  | 5.5.3 | Type safety |
-|  | 5.4.2 | Build tool ultra-rapide |
-|  | 3.4.1 | Styling utility-first |
+| Technologie | Version | Usage |
+| ------------------------------------------------------------------------------------------------------------------ | ------- | ----------------------- |
+|  | 18.3.1 | Framework UI |
+|  | 5.5.3 | Type safety |
+|  | 5.4.2 | Build tool ultra-rapide |
+|  | 3.4.1 | Styling utility-first |
### ♟️ Chess Logic
-| Librairie | Version | Usage |
-|-----------|---------|-------|
-|  | 1.0.0-beta.7 | Logique d'échecs |
-|  | 16.0.0 | Moteur d'analyse NNUE |
-|  | 4.7.3 | Échiquier interactif |
-|  | 1.4.18 | Parsing PGN robuste |
+| Librairie | Version | Usage |
+| ---------------------------------------------------------------------------------------------- | ------------ | --------------------- |
+|  | 1.0.0-beta.7 | Logique d'échecs |
+|  | 16.0.0 | Moteur d'analyse NNUE |
+|  | 4.7.3 | Échiquier interactif |
+|  | 1.4.18 | Parsing PGN robuste |
### 🎨 UI/UX
-| Librairie | Version | Usage |
-|-----------|---------|-------|
-|  | 0.344.0 | Icons modernes |
-|  | 16.0.1 | Drag & Drop |
-|  | 14.2.3 | Upload fichiers |
-|  | 10.0.4 | Notifications toast |
-|  | 2.9.6 | Accessibilité modal |
-|  | 4.5.0 | Graphiques |
+| Librairie | Version | Usage |
+| ---------------------------------------------------------------------------------------------------------- | ------- | ------------------- |
+|  | 0.344.0 | Icons modernes |
+|  | 16.0.1 | Drag & Drop |
+|  | 14.2.3 | Upload fichiers |
+|  | 10.0.4 | Notifications toast |
+|  | 2.9.6 | Accessibilité modal |
+|  | 4.5.0 | Graphiques |
### 📊 State & Data
-| Librairie | Version | Usage |
-|-----------|---------|-------|
-|  | 4.4.7 | State management |
-|  | 5.17.0 | Server state & cache |
-|  | 8.1.0 | Rate limiting API |
+| Librairie | Version | Usage |
+| --------------------------------------------------------------------------------------------------------------------- | ------- | -------------------- |
+|  | 4.4.7 | State management |
+|  | 5.17.0 | Server state & cache |
+|  | 8.1.0 | Rate limiting API |
### 🌍 i18n & Monitoring
-| Librairie | Version | Usage |
-|-----------|---------|-------|
-|  | 25.3.1 | Internationalisation |
-|  | 7.99.0 | Error tracking & monitoring |
+| Librairie | Version | Usage |
+| ------------------------------------------------------------------------------------------------------- | ------- | --------------------------- |
+|  | 25.3.1 | Internationalisation |
+|  | 7.99.0 | Error tracking & monitoring |
### 🧪 Dev Tools
-| Outil | Version | Usage |
-|-------|---------|-------|
-|  | 9.9.1 | Linting |
-|  | 1.40.1 | Tests E2E |
-|  | 8.3.0 | Linting TypeScript |
+| Outil | Version | Usage |
+| ---------------------------------------------------------------------------------------------------------------- | ------- | ------------------ |
+|  | 9.9.1 | Linting |
+|  | 1.40.1 | Tests E2E |
+|  | 8.3.0 | Linting TypeScript |
### 🚀 PWA & Export
-| Librairie | Version | Usage |
-|-----------|---------|-------|
-|  | 0.17.4 | Progressive Web App |
-|  | 1.4.1 | Screenshots |
-|  | 0.4.5 | Création GIF |
-|  | 2.11.0 | Sécurité HTML |
+| Librairie | Version | Usage |
+| --------------------------------------------------------------------------------------- | ------- | ------------------- |
+|  | 0.17.4 | Progressive Web App |
+|  | 1.4.1 | Screenshots |
+|  | 0.4.5 | Création GIF |
+|  | 2.11.0 | Sécurité HTML |
---
@@ -645,6 +777,7 @@ npm run build
```
Les fichiers optimisés seront générés dans le dossier `dist/` :
+
- Minification JavaScript/CSS
- Tree-shaking automatique
- Code splitting
@@ -659,32 +792,32 @@ Les fichiers optimisés seront générés dans le dossier `dist/` :
L'application propose **13 vues** accessibles via le **Start Menu** :
-| Touche | Vue | Description |
-|--------|-----|-------------|
-| `1` | **Games Database** | Base de données de parties |
-| `2` | **Players Database** | Gestion des joueurs |
-| `3` | **Openings Encyclopedia** | Encyclopédie d'ouvertures |
-| `4` | **Play vs Computer** | Jouer contre Stockfish |
-| `5` | **Puzzle Trainer** | Entraînement tactique |
-| `6` | **Daily Puzzle** | Puzzle quotidien |
-| `7` | **Puzzle Rush** | Mode contre-la-montre |
-| `8` | **Puzzle Battle** | Bataille de puzzles |
-| `9` | **Spectator Mode** | Mode spectateur |
-| `10` | **Learning** | Cours et leçons |
-| `11` | **Video Library** | Bibliothèque vidéo |
-| `12` | **Tournaments** | Système de tournois |
-| `13` | **Leaderboards** | Classements |
+| Touche | Vue | Description |
+| ------ | ------------------------- | -------------------------- |
+| `1` | **Games Database** | Base de données de parties |
+| `2` | **Players Database** | Gestion des joueurs |
+| `3` | **Openings Encyclopedia** | Encyclopédie d'ouvertures |
+| `4` | **Play vs Computer** | Jouer contre Stockfish |
+| `5` | **Puzzle Trainer** | Entraînement tactique |
+| `6` | **Daily Puzzle** | Puzzle quotidien |
+| `7` | **Puzzle Rush** | Mode contre-la-montre |
+| `8` | **Puzzle Battle** | Bataille de puzzles |
+| `9` | **Spectator Mode** | Mode spectateur |
+| `10` | **Learning** | Cours et leçons |
+| `11` | **Video Library** | Bibliothèque vidéo |
+| `12` | **Tournaments** | Système de tournois |
+| `13` | **Leaderboards** | Classements |
### ⌨️ Raccourcis Clavier
-| Raccourci | Action |
-|-----------|--------|
+| Raccourci | Action |
+| --------- | ------------------------------------ |
| `←` / `→` | Navigation coups (précédent/suivant) |
-| `Alt + G` | Vue Games Database |
-| `Alt + P` | Vue Players Database |
-| `Alt + O` | Vue Openings |
-| `Esc` | Fermer modal |
-| `F11` | Plein écran |
+| `Alt + G` | Vue Games Database |
+| `Alt + P` | Vue Players Database |
+| `Alt + O` | Vue Openings |
+| `Esc` | Fermer modal |
+| `F11` | Plein écran |
### 📥 Import de Parties
@@ -729,6 +862,7 @@ L'application propose **13 vues** accessibles via le **Start Menu** :
### 🧩 Résoudre des Puzzles
#### Mode Trainer
+
1. Aller dans **Puzzle Trainer** (touche `5`)
2. Choisir un thème tactique
3. Résoudre le puzzle en jouant les coups
@@ -736,6 +870,7 @@ L'application propose **13 vues** accessibles via le **Start Menu** :
5. Explication détaillée si erreur
#### Daily Puzzle
+
1. Aller dans **Daily Puzzle** (touche `6`)
2. Un nouveau puzzle chaque jour
3. Maintenir votre **streak** quotidien
@@ -762,15 +897,18 @@ L'application propose **13 vues** accessibles via le **Start Menu** :
### 📲 Installation
#### Desktop (Chrome/Edge)
+
1. Ouvrir l'application dans le navigateur
2. Cliquer sur l'icône **"+""** dans la barre d'adresse
3. Confirmer l'installation
#### Mobile (Android)
+
1. Ouvrir l'application dans Chrome
2. Menu ⋮ → **"Ajouter à l'écran d'accueil"**
#### iOS (Safari)
+
1. Ouvrir l'application dans Safari
2. Partager → **"Sur l'écran d'accueil"**
@@ -786,11 +924,11 @@ L'application propose **13 vues** accessibles via le **Start Menu** :
### Langues Supportées
-| Langue | Code | État |
-|--------|------|------|
+| Langue | Code | État |
+| ----------- | ---- | ------------------- |
| 🇫🇷 Français | `fr` | ✅ Complet (défaut) |
-| 🇬🇧 English | `en` | ✅ Complet |
-| 🇪🇸 Español | `es` | ✅ Complet |
+| 🇬🇧 English | `en` | ✅ Complet |
+| 🇪🇸 Español | `es` | ✅ Complet |
### Configuration
@@ -824,18 +962,21 @@ L'application propose **13 vues** accessibles via le **Start Menu** :
L'application respecte les standards **WCAG 2.1 Level AA** :
#### ⌨️ Navigation Clavier
+
- **Tab** : Navigation entre éléments
- **Enter/Space** : Activation des boutons
- **Escape** : Fermeture des modals
- **Arrow keys** : Navigation dans les coups
#### 🔒 Focus Management
+
- **Focus Lock** dans les modals (react-focus-lock)
- **Focus trap** : Empêche la sortie du modal
- **Restauration du focus** après fermeture
- **Indicateurs visuels** clairs
#### 🏷️ ARIA & Sémantique
+
- **ARIA labels** sur tous les boutons interactifs
- **Rôles sémantiques** appropriés (`dialog`, `button`, `navigation`)
- **Live regions** pour les mises à jour dynamiques
@@ -843,18 +984,21 @@ L'application respecte les standards **WCAG 2.1 Level AA** :
- **aria-hidden** sur les icônes décoratives
#### 📱 Responsive & Touch
+
- **Mobile-first** design
- **Touch-friendly** (cibles >= 44x44px)
- **Responsive** sur tous les breakpoints
- **Zoom** : Support jusqu'à 200%
#### 🎨 Contraste & Visibilité
+
- **Contraste minimum** : 4.5:1 (texte normal)
- **Contraste amélioré** : 7:1 (titres)
- **Mode sombre** avec contrastes adaptés
- **Pas de dépendance à la couleur** seule
#### 🔊 Screen Readers
+
- **Compatible** NVDA, JAWS, VoiceOver
- **Descriptions** claires et concises
- **Annonces** de changements d'état
@@ -941,14 +1085,14 @@ Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
new Sentry.BrowserTracing({
- tracePropagationTargets: ["localhost", "yourdomain.com"],
+ tracePropagationTargets: ['localhost', 'yourdomain.com'],
}),
new Sentry.Replay({
maskAllText: false,
blockAllMedia: false,
}),
],
- tracesSampleRate: 0.1, // 10% des transactions
+ tracesSampleRate: 0.1, // 10% des transactions
replaysSessionSampleRate: 0.1, // 10% des sessions
replaysOnErrorSampleRate: 1.0, // 100% en cas d'erreur
beforeSend(event) {
@@ -977,6 +1121,7 @@ Sentry.init({
#### Monitoring en Production
Dashboard Sentry affiche :
+
- Taux d'erreur
- Erreurs les plus fréquentes
- Performance des transactions
@@ -1009,6 +1154,7 @@ Les contributions sont les bienvenues ! Voici comment participer :
### 📝 Guidelines
#### Code Style
+
- **ESLint** : Respecter la configuration ESLint
- **TypeScript** : Typage strict obligatoire
- **Naming** :
@@ -1017,9 +1163,11 @@ Les contributions sont les bienvenues ! Voici comment participer :
- Constants: UPPER_SNAKE_CASE
#### Commits
+
Format : `type(scope): message`
Types :
+
- `feat`: Nouvelle fonctionnalité
- `fix`: Correction de bug
- `docs`: Documentation
@@ -1029,6 +1177,7 @@ Types :
- `chore`: Maintenance, dépendances
Exemples :
+
```bash
feat(analyzer): add multi-pv support
fix(import): handle malformed PGN files
@@ -1036,10 +1185,12 @@ docs(readme): update installation instructions
```
#### Tests
+
- Ajouter des **tests E2E** pour les nouvelles features
- S'assurer que tous les tests passent avant PR
#### Documentation
+
- Commenter le **code complexe**
- Mettre à jour le **README** si nécessaire
- Ajouter des **types TypeScript** complets
@@ -1047,6 +1198,7 @@ docs(readme): update installation instructions
### 🐛 Reporter un Bug
Utilisez les [GitHub Issues](https://github.com/phuetz/ChessDatabase/issues) avec :
+
- Description claire du problème
- Steps to reproduce
- Comportement attendu vs actuel
@@ -1056,6 +1208,7 @@ Utilisez les [GitHub Issues](https://github.com/phuetz/ChessDatabase/issues) ave
### 💡 Proposer une Feature
Ouvrir une issue avec :
+
- Description de la feature
- Use case / problème résolu
- Proposition d'implémentation
@@ -1117,6 +1270,7 @@ Un grand merci aux projets open source qui rendent cette application possible :
### ♟️ Communauté Échecs
Merci à la communauté mondiale des échecs pour :
+
- Les parties historiques annotées
- Les bases de données d'ouvertures
- Les ressources éducatives
@@ -1193,7 +1347,7 @@ Pour obtenir de l'aide :
### 🎯 Fait avec 💙 par des passionnés d'échecs pour la communauté
-**Chess Master Database** - *Élevez votre jeu au niveau supérieur*
+**Chess Master Database** - _Élevez votre jeu au niveau supérieur_
---
diff --git a/package-lock.json b/package-lock.json
index dec5401..377bfd7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,23 +35,48 @@
"devDependencies": {
"@eslint/js": "^9.9.1",
"@playwright/test": "^1.40.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
+ "@vitest/ui": "^4.0.9",
"autoprefixer": "^10.4.18",
"eslint": "^9.9.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
+ "husky": "^9.1.7",
+ "jsdom": "^27.2.0",
+ "lint-staged": "^16.2.6",
"postcss": "^8.4.35",
+ "prettier": "^3.6.2",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2",
- "vite-plugin-pwa": "^0.17.4"
+ "vite-bundle-visualizer": "^1.2.1",
+ "vite-plugin-compression": "^0.5.1",
+ "vite-plugin-pwa": "^0.17.4",
+ "vitest": "^4.0.9"
}
},
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.23",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz",
+ "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -77,6 +102,61 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
+ "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.7.4",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
+ "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.2"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1618,6 +1698,141 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.16.tgz",
+ "integrity": "sha512-2SpS4/UaWQaGpBINyG5ZuCHnUDeVByOhvbkARwfmnfxDvTaj80yOI1cD8Tw93ICV5Fx4fnyDKWQZI1CDtcWyUg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1890,6 +2105,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
@@ -1906,6 +2138,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
@@ -1922,6 +2171,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@@ -2234,10 +2500,11 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -2334,6 +2601,13 @@
"node": ">=18"
}
},
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
@@ -2434,208 +2708,308 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
- "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
+ "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
- "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
+ "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
- "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
+ "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
- "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
+ "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
+ "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
+ "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
- "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
+ "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
- "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
+ "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
- "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
+ "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
- "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
+ "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
+ "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
- "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
+ "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
- "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
+ "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
+ "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
- "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
+ "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
- "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
- "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
+ "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
+ "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
- "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
+ "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
- "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
+ "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
- "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
+ "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -2786,6 +3160,13 @@
"node": ">=8"
}
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -2825,17 +3206,126 @@
"react": "^18 || ^19"
}
},
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
+ "license": "MIT",
+ "peer": true,
"dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
+ "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
@@ -2866,11 +3356,30 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "dev": true
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@@ -3167,6 +3676,122 @@
"vite": "^4.2.0 || ^5.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.9.tgz",
+ "integrity": "sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.0.9",
+ "@vitest/utils": "4.0.9",
+ "chai": "^6.2.0",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.9.tgz",
+ "integrity": "sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.9.tgz",
+ "integrity": "sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.0.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.9.tgz",
+ "integrity": "sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.9.tgz",
+ "integrity": "sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/ui": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.9.tgz",
+ "integrity": "sha512-6HV2HHl9aRJ09TlYj/WAQxaa797Ezb5u0LpgabthlASAUAWKgw/W1DSPX7t848mMZmIUvzZgnUHGIylAoYHP0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.0.9",
+ "fflate": "^0.8.2",
+ "flatted": "^3.3.3",
+ "pathe": "^2.0.3",
+ "sirv": "^3.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "4.0.9"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.9.tgz",
+ "integrity": "sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.9",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -3189,6 +3814,16 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3205,6 +3840,22 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
+ "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@@ -3374,6 +4025,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ast-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -3547,6 +4208,16 @@
"node": ">= 0.6.0"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3622,6 +4293,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -3711,6 +4392,16 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz",
+ "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3781,73 +4472,208 @@
"node": ">= 6"
}
},
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/cli-truncate": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
+ "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
+ "slice-ansi": "^7.1.0",
+ "string-width": "^8.0.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/cli-truncate/node_modules/string-width": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
+ "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
"engines": {
- "node": ">= 6"
+ "node": ">=12"
}
},
- "node_modules/common-tags": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
- "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=4.0.0"
+ "node": ">=8"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/core-js-compat": {
- "version": "3.43.0",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz",
- "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==",
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "browserslist": "^4.25.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/common-tags": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
+ "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.43.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz",
+ "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.0"
},
"funding": {
"type": "opencollective",
@@ -3888,6 +4714,27 @@
"utrie": "^1.0.2"
}
},
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -3900,6 +4747,21 @@
"node": ">=4"
}
},
+ "node_modules/cssstyle": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz",
+ "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.0.3",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.14",
+ "css-tree": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -3913,6 +4775,57 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/data-urls/node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/data-urls/node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/data-urls/node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -3968,10 +4881,11 @@
}
},
"node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
@@ -3984,6 +4898,13 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -4017,6 +4938,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -4035,6 +4966,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -4063,6 +5005,14 @@
"redux": "^4.2.0"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -4180,6 +5130,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@@ -4269,6 +5232,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -4600,6 +5570,16 @@
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4671,6 +5651,13 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4770,10 +5757,11 @@
}
},
"node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
- "dev": true
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/focus-lock": {
"version": "1.3.6",
@@ -4918,6 +5906,29 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -5201,6 +6212,19 @@
"react-is": "^16.7.0"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
@@ -5242,6 +6266,50 @@
"entities": "^4.4.0"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/husky": {
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "husky": "bin.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/i18next": {
"version": "25.3.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.1.tgz",
@@ -5273,9 +6341,22 @@
}
}
},
- "node_modules/idb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
"dev": true,
"license": "ISC"
@@ -5311,6 +6392,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-from-esm": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz",
+ "integrity": "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "import-meta-resolve": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.20"
+ }
+ },
+ "node_modules/import-meta-resolve": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
+ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -5320,6 +6426,16 @@
"node": ">=0.8.19"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -5500,6 +6616,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -5643,6 +6775,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -5811,6 +6950,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -5884,6 +7036,83 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdom": {
+ "version": "27.2.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.2.0.tgz",
+ "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@acemir/cssom": "^0.9.23",
+ "@asamuzakjp/dom-selector": "^6.7.4",
+ "cssstyle": "^5.3.3",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^8.0.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.1.0",
+ "ws": "^8.18.3",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/jsdom/node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/jsdom/node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/jsesc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@@ -6052,6 +7281,115 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
+ "node_modules/lint-staged": {
+ "version": "16.2.6",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz",
+ "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^14.0.1",
+ "listr2": "^9.0.5",
+ "micromatch": "^4.0.8",
+ "nano-spawn": "^2.0.0",
+ "pidtree": "^0.6.0",
+ "string-argv": "^0.3.2",
+ "yaml": "^2.8.1"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/commander": {
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
+ "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
+ "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cli-truncate": "^5.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/listr2/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/listr2/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/localforage": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
@@ -6103,6 +7441,82 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/log-update": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-update/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -6131,6 +7545,17 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@@ -6151,6 +7576,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -6173,6 +7605,29 @@
"node": ">=8.6"
}
},
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -6194,6 +7649,16 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6211,6 +7676,19 @@
"thenify-all": "^1.0.0"
}
},
+ "node_modules/nano-spawn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
+ "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -6369,23 +7847,57 @@
"wrappy": "1"
}
},
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
+ "mimic-function": "^5.0.0"
},
"engines": {
- "node": ">= 0.8.0"
- }
- },
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
@@ -6486,6 +7998,32 @@
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
"license": "MIT"
},
+ "node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6542,6 +8080,13 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6560,6 +8105,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -6636,9 +8194,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -6653,9 +8211,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -6786,6 +8345,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-bytes": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
@@ -6799,6 +8374,55 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -7068,6 +8692,20 @@
"node": ">=8.10.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
@@ -7178,6 +8816,16 @@
"regjsparser": "bin/parser"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -7214,6 +8862,23 @@
"node": ">=4"
}
},
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -7224,13 +8889,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/rollup": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
- "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
+ "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@types/estree": "1.0.6"
+ "@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -7240,25 +8913,85 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.24.0",
- "@rollup/rollup-android-arm64": "4.24.0",
- "@rollup/rollup-darwin-arm64": "4.24.0",
- "@rollup/rollup-darwin-x64": "4.24.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
- "@rollup/rollup-linux-arm64-gnu": "4.24.0",
- "@rollup/rollup-linux-arm64-musl": "4.24.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
- "@rollup/rollup-linux-s390x-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-musl": "4.24.0",
- "@rollup/rollup-win32-arm64-msvc": "4.24.0",
- "@rollup/rollup-win32-ia32-msvc": "4.24.0",
- "@rollup/rollup-win32-x64-msvc": "4.24.0",
+ "@rollup/rollup-android-arm-eabi": "4.53.2",
+ "@rollup/rollup-android-arm64": "4.53.2",
+ "@rollup/rollup-darwin-arm64": "4.53.2",
+ "@rollup/rollup-darwin-x64": "4.53.2",
+ "@rollup/rollup-freebsd-arm64": "4.53.2",
+ "@rollup/rollup-freebsd-x64": "4.53.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.2",
+ "@rollup/rollup-linux-arm64-musl": "4.53.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-musl": "4.53.2",
+ "@rollup/rollup-openharmony-arm64": "4.53.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.2",
+ "@rollup/rollup-win32-x64-gnu": "4.53.2",
+ "@rollup/rollup-win32-x64-msvc": "4.53.2",
"fsevents": "~2.3.2"
}
},
+ "node_modules/rollup-plugin-visualizer": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz",
+ "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "open": "^8.4.0",
+ "picomatch": "^4.0.2",
+ "source-map": "^0.7.4",
+ "yargs": "^17.5.1"
+ },
+ "bin": {
+ "rollup-plugin-visualizer": "dist/bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "rolldown": "1.x",
+ "rollup": "2.x || 3.x || 4.x"
+ },
+ "peerDependenciesMeta": {
+ "rolldown": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7358,6 +9091,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/sanitize-html": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz",
@@ -7384,6 +9124,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -7557,6 +9310,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -7569,6 +9329,67 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/smob": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
@@ -7626,6 +9447,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/stockfish": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/stockfish/-/stockfish-16.0.0.tgz",
@@ -7649,6 +9484,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -7872,6 +9717,19 @@
"node": ">=10"
}
},
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -7931,6 +9789,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -8060,10 +9925,112 @@
"node": ">=0.8"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz",
+ "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.17"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz",
+ "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tmp": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
+ "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
@@ -8072,6 +10039,29 @@
"node": ">=8.0"
}
},
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@@ -8373,98 +10363,824 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.19",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
+ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-bundle-visualizer": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/vite-bundle-visualizer/-/vite-bundle-visualizer-1.2.1.tgz",
+ "integrity": "sha512-cwz/Pg6+95YbgIDp+RPwEToc4TKxfsFWSG/tsl2DSZd9YZicUag1tQXjJ5xcL7ydvEoaC2FOZeaXOU60t9BRXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "import-from-esm": "^1.3.3",
+ "rollup-plugin-visualizer": "^5.11.0",
+ "tmp": "^0.2.1"
+ },
+ "bin": {
+ "vite-bundle-visualizer": "bin.js"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ }
+ },
+ "node_modules/vite-plugin-compression": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
+ "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "debug": "^4.3.3",
+ "fs-extra": "^10.0.0"
+ },
+ "peerDependencies": {
+ "vite": ">=2.0.0"
+ }
+ },
+ "node_modules/vite-plugin-compression/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-plugin-pwa": {
+ "version": "0.17.5",
+ "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.5.tgz",
+ "integrity": "sha512-UxRNPiJBzh4tqU/vc8G2TxmrUTzT6BqvSzhszLk62uKsf+npXdvLxGDz9C675f4BJi6MbD2tPnJhi5txlMzxbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "pretty-bytes": "^6.1.1",
+ "workbox-build": "^7.0.0",
+ "workbox-window": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^3.1.0 || ^4.0.0 || ^5.0.0",
+ "workbox-build": "^7.0.0",
+ "workbox-window": "^7.0.0"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.9.tgz",
+ "integrity": "sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.0.9",
+ "@vitest/mocker": "4.0.9",
+ "@vitest/pretty-format": "4.0.9",
+ "@vitest/runner": "4.0.9",
+ "@vitest/snapshot": "4.0.9",
+ "@vitest/spy": "4.0.9",
+ "@vitest/utils": "4.0.9",
+ "debug": "^4.4.3",
+ "es-module-lexer": "^1.7.0",
+ "expect-type": "^1.2.2",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^3.10.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.0.9",
+ "@vitest/browser-preview": "4.0.9",
+ "@vitest/browser-webdriverio": "4.0.9",
+ "@vitest/ui": "4.0.9",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "punycode": "^2.1.0"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/use-callback-ref": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
- "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.9.tgz",
+ "integrity": "sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "tslib": "^2.0.0"
+ "@vitest/spy": "4.0.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
},
- "engines": {
- "node": ">=10"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
- "@types/react": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
"optional": true
}
}
},
- "node_modules/use-sidecar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
- "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "node_modules/vitest/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "dependencies": {
- "detect-node-es": "^1.1.0",
- "tslib": "^2.0.0"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=10"
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/vitest/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/vitest/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
},
"peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ "picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
- "@types/react": {
+ "picomatch": {
"optional": true
}
}
},
- "node_modules/use-sync-external-store": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
- "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "node_modules/vitest/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
"license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
- },
- "node_modules/utrie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
- "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "base64-arraybuffer": "^1.0.2"
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vite": {
- "version": "5.4.19",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
- "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "node_modules/vitest/node_modules/vite": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
+ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -8473,19 +11189,25 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
"lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
+ "jiti": {
+ "optional": true
+ },
"less": {
"optional": true
},
@@ -8506,34 +11228,15 @@
},
"terser": {
"optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
}
}
},
- "node_modules/vite-plugin-pwa": {
- "version": "0.17.5",
- "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.5.tgz",
- "integrity": "sha512-UxRNPiJBzh4tqU/vc8G2TxmrUTzT6BqvSzhszLk62uKsf+npXdvLxGDz9C675f4BJi6MbD2tPnJhi5txlMzxbQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "pretty-bytes": "^6.1.1",
- "workbox-build": "^7.0.0",
- "workbox-window": "^7.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- },
- "peerDependencies": {
- "vite": "^3.1.0 || ^4.0.0 || ^5.0.0",
- "workbox-build": "^7.0.0",
- "workbox-window": "^7.0.0"
- }
- },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
@@ -8543,6 +11246,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -8550,6 +11266,29 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/whatwg-url": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
@@ -8666,6 +11405,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -9140,6 +11896,55 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -9147,15 +11952,90 @@
"dev": true
},
"node_modules/yaml": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
- "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
+ "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"dev": true,
+ "license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
- "node": ">= 14"
+ "node": ">= 14.6"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/yocto-queue": {
diff --git a/package.json b/package.json
index bf95e08..a1d5673 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,25 @@
"scripts": {
"dev": "vite",
"build": "vite build",
+ "build:analyze": "vite-bundle-visualizer",
+ "build:prod": "npm run lint && npm run test:coverage && vite build",
"lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"preview": "vite preview",
- "test:e2e": "playwright test"
+ "test": "vitest",
+ "test:ui": "vitest --ui",
+ "test:coverage": "vitest --coverage",
+ "test:e2e": "playwright test",
+ "test:e2e:headed": "playwright test --headed",
+ "test:e2e:debug": "playwright test --debug",
+ "type-check": "tsc --noEmit",
+ "validate": "npm run lint && npm run type-check && npm test -- --run",
+ "clean": "rm -rf dist node_modules/.vite",
+ "clean:all": "rm -rf dist node_modules coverage playwright-report",
+ "prepare": "husky",
+ "postinstall": "playwright install --with-deps chromium"
},
"dependencies": {
"@mliebelt/pgn-parser": "^1.4.18",
@@ -38,20 +54,40 @@
"devDependencies": {
"@eslint/js": "^9.9.1",
"@playwright/test": "^1.40.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
+ "@vitest/ui": "^4.0.9",
"autoprefixer": "^10.4.18",
"eslint": "^9.9.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
+ "husky": "^9.1.7",
+ "jsdom": "^27.2.0",
+ "lint-staged": "^16.2.6",
"postcss": "^8.4.35",
+ "prettier": "^3.6.2",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2",
- "vite-plugin-pwa": "^0.17.4"
+ "vite-bundle-visualizer": "^1.2.1",
+ "vite-plugin-compression": "^0.5.1",
+ "vite-plugin-pwa": "^0.17.4",
+ "vitest": "^4.0.9"
+ },
+ "lint-staged": {
+ "*.{ts,tsx,js,jsx}": [
+ "eslint --fix",
+ "prettier --write"
+ ],
+ "*.{json,css,md}": [
+ "prettier --write"
+ ]
}
}
diff --git a/src/App.tsx b/src/App.tsx
index c8ce346..e5688e2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,6 +6,7 @@ import { masterGames } from './data/masterGames';
import ChessMasterDatabase from './components/ChessMasterDatabase';
import StartMenu from './components/StartMenu';
import NotificationCenter from './components/NotificationCenter';
+import ErrorBoundary from './components/ErrorBoundary';
// Initialize Sentry
initSentry();
@@ -35,7 +36,7 @@ const App = () => {
addNotification({
id: 'tournament',
message: 'Un nouveau tournoi débute dans 10 minutes !',
- type: 'info'
+ type: 'info',
});
}, 10000);
@@ -43,7 +44,7 @@ const App = () => {
addNotification({
id: 'challenge',
message: 'Vous avez reçu un nouveau défi !',
- type: 'info'
+ type: 'info',
});
}, 5000);
@@ -54,15 +55,15 @@ const App = () => {
}, [addNotification]);
return (
-
-
- {view === 'menu' ? : }
-
-
-
+
+
+
+ {view === 'menu' ? : }
+
+
+
+
);
};
-export default App;
\ No newline at end of file
+export default App;
diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..29f097f
--- /dev/null
+++ b/src/components/ErrorBoundary.tsx
@@ -0,0 +1,146 @@
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
+
+interface Props {
+ children: ReactNode;
+ fallback?: ReactNode;
+}
+
+interface State {
+ hasError: boolean;
+ error: Error | null;
+ errorInfo: ErrorInfo | null;
+}
+
+/**
+ * ErrorBoundary component to catch and handle React errors gracefully
+ * Displays a user-friendly error message and provides recovery options
+ */
+class ErrorBoundary extends Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ };
+ }
+
+ static getDerivedStateFromError(error: Error): Partial {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
+ this.setState({
+ error,
+ errorInfo,
+ });
+
+ // Log to error tracking service if configured
+ if (window.Sentry) {
+ window.Sentry.captureException(error, {
+ contexts: {
+ react: {
+ componentStack: errorInfo.componentStack,
+ },
+ },
+ });
+ }
+ }
+
+ handleReset = () => {
+ this.setState({
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ });
+ };
+
+ handleReload = () => {
+ window.location.reload();
+ };
+
+ handleGoHome = () => {
+ window.location.href = '/';
+ };
+
+ render() {
+ if (this.state.hasError) {
+ if (this.props.fallback) {
+ return this.props.fallback;
+ }
+
+ return (
+
+
+
+
+
+
+ Oops! Something went wrong
+
+
+ We encountered an unexpected error
+
+
+
+
+
+
+ Error Details:
+
+
+ {this.state.error?.toString()}
+
+ {process.env.NODE_ENV === 'development' && this.state.errorInfo && (
+
+
+ View stack trace
+
+
+ {this.state.errorInfo.componentStack}
+
+
+ )}
+
+
+
+
+
+ Try Again
+
+
+
+ Reload Page
+
+
+
+ Go Home
+
+
+
+
+ If the problem persists, please contact support or try clearing your browser cache.
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/src/components/KeyboardShortcutsModal.tsx b/src/components/KeyboardShortcutsModal.tsx
new file mode 100644
index 0000000..60d52e0
--- /dev/null
+++ b/src/components/KeyboardShortcutsModal.tsx
@@ -0,0 +1,187 @@
+import React from 'react';
+import AccessibleModal from './AccessibleModal';
+import { Keyboard } from 'lucide-react';
+
+interface KeyboardShortcutsModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+interface Shortcut {
+ keys: string[];
+ description: string;
+ category: string;
+}
+
+const KeyboardShortcutsModal: React.FC = ({ isOpen, onClose }) => {
+ const shortcuts: Shortcut[] = [
+ // Navigation
+ {
+ keys: ['←'],
+ description: 'Previous move',
+ category: 'Navigation',
+ },
+ {
+ keys: ['→'],
+ description: 'Next move',
+ category: 'Navigation',
+ },
+ {
+ keys: ['Home'],
+ description: 'Go to first move',
+ category: 'Navigation',
+ },
+ {
+ keys: ['End'],
+ description: 'Go to last move',
+ category: 'Navigation',
+ },
+ // Board
+ {
+ keys: ['F'],
+ description: 'Flip board',
+ category: 'Board',
+ },
+ {
+ keys: ['Alt', 'F'],
+ description: 'Toggle fullscreen',
+ category: 'Board',
+ },
+ // General
+ {
+ keys: ['?'],
+ description: 'Show keyboard shortcuts',
+ category: 'General',
+ },
+ {
+ keys: ['/'],
+ description: 'Focus search',
+ category: 'General',
+ },
+ {
+ keys: ['Esc'],
+ description: 'Close modal / Cancel',
+ category: 'General',
+ },
+ {
+ keys: ['Ctrl', 'Z'],
+ description: 'Undo move',
+ category: 'General',
+ },
+ {
+ keys: ['Ctrl', 'Y'],
+ description: 'Redo move',
+ category: 'General',
+ },
+ // Analysis
+ {
+ keys: ['A'],
+ description: 'Toggle analysis',
+ category: 'Analysis',
+ },
+ {
+ keys: ['Space'],
+ description: 'Start/Pause engine',
+ category: 'Analysis',
+ },
+ // Views
+ {
+ keys: ['1'],
+ description: 'Games database view',
+ category: 'Views',
+ },
+ {
+ keys: ['2'],
+ description: 'Openings encyclopedia view',
+ category: 'Views',
+ },
+ {
+ keys: ['3'],
+ description: 'Players database view',
+ category: 'Views',
+ },
+ ];
+
+ const categories = Array.from(new Set(shortcuts.map(s => s.category)));
+
+ return (
+
+
+
+
+
+
+
+
Keyboard Shortcuts
+
+ Master these shortcuts to improve your productivity
+
+
+
+
+
+ {categories.map(category => (
+
+
+
+ {category}
+
+
+ {shortcuts
+ .filter(s => s.category === category)
+ .map((shortcut, index) => (
+
+
+ {shortcut.description}
+
+
+ {shortcut.keys.map((key, i) => (
+
+
+ {key}
+
+ {i < shortcut.keys.length - 1 && (
+ +
+ )}
+
+ ))}
+
+
+ ))}
+
+
+ ))}
+
+
+
+
+ Pro tip: Press{' '}
+
+ ?
+ {' '}
+ anytime to view these shortcuts
+
+
+
+
+
+ Close
+
+
+
+
+ );
+};
+
+export default KeyboardShortcutsModal;
diff --git a/src/components/Skeleton.tsx b/src/components/Skeleton.tsx
new file mode 100644
index 0000000..4572611
--- /dev/null
+++ b/src/components/Skeleton.tsx
@@ -0,0 +1,169 @@
+import React from 'react';
+
+interface SkeletonProps {
+ className?: string;
+ variant?: 'text' | 'circular' | 'rectangular';
+ width?: string | number;
+ height?: string | number;
+ animation?: 'pulse' | 'wave' | 'none';
+}
+
+/**
+ * Skeleton component for loading states
+ * Provides a placeholder UI while content is loading
+ */
+export const Skeleton: React.FC = ({
+ className = '',
+ variant = 'text',
+ width,
+ height,
+ animation = 'pulse',
+}) => {
+ const baseClasses = 'bg-gray-200 dark:bg-gray-700';
+
+ const animationClasses = {
+ pulse: 'animate-pulse',
+ wave: 'animate-shimmer',
+ none: '',
+ };
+
+ const variantClasses = {
+ text: 'rounded',
+ circular: 'rounded-full',
+ rectangular: 'rounded-lg',
+ };
+
+ const style: React.CSSProperties = {
+ width: width || (variant === 'text' ? '100%' : undefined),
+ height: height || (variant === 'text' ? '1em' : undefined),
+ };
+
+ return (
+
+ );
+};
+
+/**
+ * Skeleton loader for game cards
+ */
+export const GameCardSkeleton: React.FC = () => {
+ return (
+
+ );
+};
+
+/**
+ * Skeleton loader for player cards
+ */
+export const PlayerCardSkeleton: React.FC = () => {
+ return (
+
+ );
+};
+
+/**
+ * Skeleton loader for chess board
+ */
+export const BoardSkeleton: React.FC = () => {
+ return (
+
+
+ {Array.from({ length: 64 }).map((_, i) => (
+
+ ))}
+
+
+ );
+};
+
+/**
+ * Skeleton loader for list items
+ */
+export const ListItemSkeleton: React.FC<{ count?: number }> = ({ count = 5 }) => {
+ return (
+ <>
+ {Array.from({ length: count }).map((_, i) => (
+
+ ))}
+ >
+ );
+};
+
+/**
+ * Skeleton loader for table rows
+ */
+export const TableRowSkeleton: React.FC<{ columns?: number; rows?: number }> = ({
+ columns = 4,
+ rows = 5,
+}) => {
+ return (
+ <>
+ {Array.from({ length: rows }).map((_, rowIndex) => (
+
+ {Array.from({ length: columns }).map((_, colIndex) => (
+
+
+
+ ))}
+
+ ))}
+ >
+ );
+};
+
+export default Skeleton;
diff --git a/src/constants.ts b/src/constants.ts
new file mode 100644
index 0000000..b53b6c0
--- /dev/null
+++ b/src/constants.ts
@@ -0,0 +1,201 @@
+/**
+ * Application-wide constants
+ * Centralizes magic numbers and configuration values
+ */
+
+// Stockfish Engine Configuration
+export const STOCKFISH_CONFIG = {
+ MIN_DEPTH: 10,
+ MAX_DEPTH: 18,
+ DEFAULT_DEPTH: 15,
+ MULTI_PV: 3,
+ DIFFICULTY_LEVELS: {
+ BEGINNER: 1,
+ EASY: 5,
+ MEDIUM: 10,
+ HARD: 15,
+ EXPERT: 20,
+ },
+} as const;
+
+// ELO Rating Configuration
+export const ELO_CONFIG = {
+ DEFAULT_K_FACTOR: 20,
+ DEFAULT_RATING: 1500,
+ MIN_RATING: 100,
+ MAX_RATING: 3000,
+ RATING_DIFFERENCE_SCALING: 400,
+} as const;
+
+// Chess Piece Values (in pawns)
+export const PIECE_VALUES = {
+ p: 1, // Pawn
+ n: 3, // Knight
+ b: 3, // Bishop
+ r: 5, // Rook
+ q: 9, // Queen
+ k: 0, // King
+} as const;
+
+// UI Configuration
+export const UI_CONFIG = {
+ // Toast/Notification durations (ms)
+ NOTIFICATION_DURATION: 5000,
+ SHORT_NOTIFICATION: 2000,
+ LONG_NOTIFICATION: 10000,
+
+ // Animation durations (ms)
+ TRANSITION_DURATION: 300,
+ MODAL_FADE_IN: 200,
+
+ // Pagination
+ ITEMS_PER_PAGE: 20,
+ GAMES_PER_PAGE: 10,
+
+ // Debounce/Throttle timings (ms)
+ SEARCH_DEBOUNCE: 300,
+ RESIZE_THROTTLE: 100,
+} as const;
+
+// React Query Configuration
+export const QUERY_CONFIG = {
+ STALE_TIME: 5 * 60 * 1000, // 5 minutes
+ CACHE_TIME: 24 * 60 * 60 * 1000, // 24 hours
+ RETRY_COUNT: 2,
+ RETRY_DELAY: 1000,
+} as const;
+
+// Lichess API Configuration
+export const LICHESS_API = {
+ BASE_URL: 'https://lichess.org/api',
+ RATE_LIMIT_DELAY: 1000, // 1 second between requests
+ MAX_GAMES_PER_REQUEST: 100,
+ TIMEOUT: 10000, // 10 seconds
+} as const;
+
+// localStorage Keys
+export const STORAGE_KEYS = {
+ CHESS_STORE: 'chess-store',
+ THEME_PREFERENCE: 'theme-preference',
+ LANGUAGE_PREFERENCE: 'language-preference',
+ ANALYSIS_CACHE: 'analysis-cache',
+ PUZZLE_STATS: 'puzzle-stats',
+ USER_PREFERENCES: 'user-preferences',
+} as const;
+
+// Puzzle Configuration
+export const PUZZLE_CONFIG = {
+ DAILY_PUZZLE_RESET_HOUR: 0, // Midnight UTC
+ RUSH_MODE_TIME_LIMIT: 300, // 5 minutes in seconds
+ BATTLE_MODE_TIME_PER_PUZZLE: 30, // 30 seconds per puzzle
+ MIN_RATING: 800,
+ MAX_RATING: 2800,
+ DEFAULT_PUZZLE_RATING: 1500,
+} as const;
+
+// Tournament Configuration
+export const TOURNAMENT_CONFIG = {
+ MIN_PLAYERS: 2,
+ MAX_PLAYERS: 32,
+ ROUND_ROBIN_MAX_PLAYERS: 16,
+ DEFAULT_TIME_CONTROL: '10+0', // 10 minutes, no increment
+} as const;
+
+// File Upload Configuration
+export const UPLOAD_CONFIG = {
+ MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB
+ ALLOWED_FILE_TYPES: ['.pgn', '.fen'],
+ MAX_FILES_PER_UPLOAD: 10,
+} as const;
+
+// Board Themes
+export const BOARD_THEMES = [
+ 'classic',
+ 'blue',
+ 'green',
+ 'wood',
+ 'brown',
+ 'purple',
+ 'pink',
+ 'marble',
+ 'metal',
+] as const;
+
+// UI Themes
+export const UI_THEMES = ['light', 'dark', 'system'] as const;
+
+// Supported Languages
+export const LANGUAGES = [
+ { code: 'en', name: 'English', flag: '🇬🇧' },
+ { code: 'fr', name: 'Français', flag: '🇫🇷' },
+ { code: 'es', name: 'Español', flag: '🇪🇸' },
+] as const;
+
+// Keyboard Shortcuts
+export const KEYBOARD_SHORTCUTS = {
+ NAVIGATE_NEXT: 'ArrowRight',
+ NAVIGATE_PREV: 'ArrowLeft',
+ FLIP_BOARD: 'f',
+ OPEN_HELP: '?',
+ OPEN_SEARCH: '/',
+ ESCAPE: 'Escape',
+ ENTER: 'Enter',
+ SPACE: ' ',
+} as const;
+
+// Analysis Configuration
+export const ANALYSIS_CONFIG = {
+ CACHE_EXPIRY: 7 * 24 * 60 * 60 * 1000, // 7 days
+ MAX_CACHE_ENTRIES: 1000,
+ EVALUATION_THRESHOLD: {
+ BLUNDER: 300, // centipawns
+ MISTAKE: 150,
+ INACCURACY: 50,
+ },
+} as const;
+
+// Error Messages
+export const ERROR_MESSAGES = {
+ NETWORK_ERROR: 'Network error. Please check your connection.',
+ INVALID_PGN: 'Invalid PGN format.',
+ INVALID_FEN: 'Invalid FEN notation.',
+ FILE_TOO_LARGE: 'File size exceeds maximum allowed size.',
+ UNSUPPORTED_FORMAT: 'Unsupported file format.',
+ STOCKFISH_ERROR: 'Chess engine error. Please try again.',
+ LICHESS_API_ERROR: 'Failed to fetch from Lichess API.',
+} as const;
+
+// Success Messages
+export const SUCCESS_MESSAGES = {
+ GAME_IMPORTED: 'Game imported successfully!',
+ GAME_EXPORTED: 'Game exported successfully!',
+ SETTINGS_SAVED: 'Settings saved!',
+ PUZZLE_SOLVED: 'Puzzle solved correctly!',
+ GAME_WON: 'Congratulations! You won!',
+} as const;
+
+// Regular Expressions
+export const REGEX_PATTERNS = {
+ FEN: /^([rnbqkpRNBQKP1-8]+\/){7}[rnbqkpRNBQKP1-8]+\s[bw]\s(-|K?Q?k?q?)\s(-|[a-h][36])\s\d+\s\d+$/,
+ CHESS_MOVE: /^[a-h][1-8][a-h][1-8][qrbn]?$/,
+ ECO_CODE: /^[A-E]\d{2}$/,
+ LICHESS_USERNAME: /^[a-zA-Z0-9_-]{2,20}$/,
+} as const;
+
+// Feature Flags (for gradual rollout of new features)
+export const FEATURE_FLAGS = {
+ ENABLE_AI_HINTS: true,
+ ENABLE_CLOUD_SYNC: false,
+ ENABLE_MULTIPLAYER: false,
+ ENABLE_VOICE_COMMANDS: false,
+ ENABLE_ADVANCED_STATS: true,
+} as const;
+
+// SEO and Metadata
+export const APP_METADATA = {
+ NAME: 'Chess Master Database',
+ DESCRIPTION: 'Professional chess analysis and database application',
+ VERSION: '2.0.0',
+ AUTHOR: 'Chess Master Team',
+ KEYWORDS: ['chess', 'database', 'analysis', 'stockfish', 'pgn'],
+} as const;
diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts
new file mode 100644
index 0000000..2501d0d
--- /dev/null
+++ b/src/hooks/useDebounce.ts
@@ -0,0 +1,24 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * Custom hook for debouncing values
+ * Useful for search inputs, API calls, etc.
+ * @param value - The value to debounce
+ * @param delay - Delay in milliseconds (default: 500ms)
+ * @returns The debounced value
+ */
+export function useDebounce(value: T, delay = 500): T {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts
new file mode 100644
index 0000000..c7263aa
--- /dev/null
+++ b/src/hooks/useKeyboardShortcuts.ts
@@ -0,0 +1,52 @@
+import { useEffect, useCallback } from 'react';
+
+export interface KeyboardShortcut {
+ key: string;
+ ctrlKey?: boolean;
+ altKey?: boolean;
+ shiftKey?: boolean;
+ metaKey?: boolean;
+ description: string;
+ action: () => void;
+ preventDefault?: boolean;
+}
+
+/**
+ * Custom hook for managing keyboard shortcuts
+ * @param shortcuts - Array of keyboard shortcut configurations
+ * @param enabled - Whether the shortcuts should be active (default: true)
+ */
+export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[], enabled = true) {
+ const handleKeyDown = useCallback(
+ (event: KeyboardEvent) => {
+ if (!enabled) return;
+
+ for (const shortcut of shortcuts) {
+ const keyMatches = event.key === shortcut.key;
+ const ctrlMatches = shortcut.ctrlKey === undefined || event.ctrlKey === shortcut.ctrlKey;
+ const altMatches = shortcut.altKey === undefined || event.altKey === shortcut.altKey;
+ const shiftMatches =
+ shortcut.shiftKey === undefined || event.shiftKey === shortcut.shiftKey;
+ const metaMatches = shortcut.metaKey === undefined || event.metaKey === shortcut.metaKey;
+
+ if (keyMatches && ctrlMatches && altMatches && shiftMatches && metaMatches) {
+ if (shortcut.preventDefault) {
+ event.preventDefault();
+ }
+ shortcut.action();
+ break;
+ }
+ }
+ },
+ [shortcuts, enabled]
+ );
+
+ useEffect(() => {
+ window.addEventListener('keydown', handleKeyDown);
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [handleKeyDown]);
+
+ return shortcuts;
+}
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts
new file mode 100644
index 0000000..3523d25
--- /dev/null
+++ b/src/hooks/useLocalStorage.ts
@@ -0,0 +1,74 @@
+import { useState, useEffect, useCallback } from 'react';
+
+/**
+ * Custom hook for managing localStorage with React state
+ * @param key - The localStorage key
+ * @param initialValue - The initial value if key doesn't exist
+ * @returns [value, setValue, removeValue] tuple
+ */
+export function useLocalStorage(key: string, initialValue: T) {
+ // State to store our value
+ const [storedValue, setStoredValue] = useState(() => {
+ if (typeof window === 'undefined') {
+ return initialValue;
+ }
+
+ try {
+ const item = window.localStorage.getItem(key);
+ return item ? JSON.parse(item) : initialValue;
+ } catch (error) {
+ console.error(`Error reading localStorage key "${key}":`, error);
+ return initialValue;
+ }
+ });
+
+ // Return a wrapped version of useState's setter function that persists to localStorage
+ const setValue = useCallback(
+ (value: T | ((val: T) => T)) => {
+ try {
+ // Allow value to be a function so we have same API as useState
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+ setStoredValue(valueToStore);
+
+ if (typeof window !== 'undefined') {
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
+ }
+ } catch (error) {
+ console.error(`Error setting localStorage key "${key}":`, error);
+ }
+ },
+ [key, storedValue]
+ );
+
+ // Function to remove the value from localStorage
+ const removeValue = useCallback(() => {
+ try {
+ setStoredValue(initialValue);
+ if (typeof window !== 'undefined') {
+ window.localStorage.removeItem(key);
+ }
+ } catch (error) {
+ console.error(`Error removing localStorage key "${key}":`, error);
+ }
+ }, [key, initialValue]);
+
+ // Listen for changes in other tabs/windows
+ useEffect(() => {
+ const handleStorageChange = (e: StorageEvent) => {
+ if (e.key === key && e.newValue !== null) {
+ try {
+ setStoredValue(JSON.parse(e.newValue));
+ } catch (error) {
+ console.error(`Error parsing storage event for key "${key}":`, error);
+ }
+ }
+ };
+
+ window.addEventListener('storage', handleStorageChange);
+ return () => {
+ window.removeEventListener('storage', handleStorageChange);
+ };
+ }, [key]);
+
+ return [storedValue, setValue, removeValue] as const;
+}
diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts
new file mode 100644
index 0000000..9520469
--- /dev/null
+++ b/src/hooks/useMediaQuery.ts
@@ -0,0 +1,55 @@
+import { useState, useEffect } from 'react';
+
+/**
+ * Custom hook for responsive design using media queries
+ * @param query - The media query string (e.g., '(min-width: 768px)')
+ * @returns boolean indicating if the media query matches
+ */
+export function useMediaQuery(query: string): boolean {
+ const [matches, setMatches] = useState(() => {
+ if (typeof window === 'undefined') return false;
+ return window.matchMedia(query).matches;
+ });
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+
+ const mediaQuery = window.matchMedia(query);
+ const handleChange = (event: MediaQueryListEvent) => {
+ setMatches(event.matches);
+ };
+
+ // Set initial value
+ setMatches(mediaQuery.matches);
+
+ // Modern browsers
+ if (mediaQuery.addEventListener) {
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }
+ // Legacy browsers
+ else {
+ mediaQuery.addListener(handleChange);
+ return () => mediaQuery.removeListener(handleChange);
+ }
+ }, [query]);
+
+ return matches;
+}
+
+// Convenience hooks for common breakpoints
+export function useIsMobile() {
+ return useMediaQuery('(max-width: 767px)');
+}
+
+export function useIsTablet() {
+ return useMediaQuery('(min-width: 768px) and (max-width: 1023px)');
+}
+
+export function useIsDesktop() {
+ return useMediaQuery('(min-width: 1024px)');
+}
+
+export function useIsDarkMode() {
+ return useMediaQuery('(prefers-color-scheme: dark)');
+}
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 0000000..1141eed
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,42 @@
+import { afterEach, vi } from 'vitest';
+import { cleanup } from '@testing-library/react';
+import '@testing-library/jest-dom/vitest';
+
+// Cleanup after each test
+afterEach(() => {
+ cleanup();
+});
+
+// Mock window.matchMedia
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+});
+
+// Mock IntersectionObserver
+global.IntersectionObserver = class IntersectionObserver {
+ constructor() {}
+ disconnect() {}
+ observe() {}
+ takeRecords() {
+ return [];
+ }
+ unobserve() {}
+} as typeof IntersectionObserver;
+
+// Mock ResizeObserver
+global.ResizeObserver = class ResizeObserver {
+ constructor() {}
+ disconnect() {}
+ observe() {}
+ unobserve() {}
+} as typeof ResizeObserver;
diff --git a/src/utils/capturedPieces.test.ts b/src/utils/capturedPieces.test.ts
new file mode 100644
index 0000000..c122d57
--- /dev/null
+++ b/src/utils/capturedPieces.test.ts
@@ -0,0 +1,111 @@
+import { describe, it, expect } from 'vitest';
+import { getCapturedPieces } from './capturedPieces';
+
+describe('capturedPieces', () => {
+ const startFen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+
+ describe('getCapturedPieces', () => {
+ it('should return empty arrays for starting position', () => {
+ const captured = getCapturedPieces(startFen, startFen);
+ expect(captured.white).toEqual([]);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect single black pawn captured by white', () => {
+ const currentFen = 'rnbqkbnr/ppp1pppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['p']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect single white pawn captured by black', () => {
+ const currentFen = 'rnbqkbnr/pppppppp/8/8/8/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual([]);
+ expect(captured.black).toEqual(['p']);
+ });
+
+ it('should detect multiple pawns captured', () => {
+ const currentFen = 'rnbqkbnr/pp2pppp/8/8/8/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toHaveLength(2);
+ expect(captured.white).toEqual(['p', 'p']);
+ expect(captured.black).toHaveLength(1);
+ expect(captured.black).toEqual(['p']);
+ });
+
+ it('should detect captured knight', () => {
+ const currentFen = 'r1bqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['n']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect captured bishop', () => {
+ const currentFen = 'rnbqk1nr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['b']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect captured rook', () => {
+ const currentFen = 'rnbqkbn1/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['r']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect captured queen', () => {
+ const currentFen = 'rnb1kbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['q']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should detect multiple different pieces captured', () => {
+ const currentFen = 'r1b1kbnr/pp1ppppp/8/8/8/8/PPP1PPPP/RNB1KBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ // White captured: 1 knight, 2 pawns, 1 queen
+ expect(captured.white).toHaveLength(4);
+ expect(captured.white).toContain('n');
+ expect(captured.white).toContain('p');
+ expect(captured.white).toContain('q');
+ // Black captured: 1 pawn, 1 queen
+ expect(captured.black).toHaveLength(2);
+ expect(captured.black).toContain('p');
+ expect(captured.black).toContain('q');
+ });
+
+ it('should handle endgame with many captures', () => {
+ const currentFen = '4k3/8/8/8/8/8/8/4K3 w - - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ // All pieces captured except kings
+ expect(captured.white).toHaveLength(15); // 8 pawns + 2 rooks + 2 knights + 2 bishops + 1 queen
+ expect(captured.black).toHaveLength(15);
+ });
+
+ it('should correctly handle positions with promoted pawns', () => {
+ // Start with standard position, end with promoted queen
+ const currentFen = 'rnbqkbnr/ppppppp1/8/8/8/8/PPPPPPPP/RNBQKBNQ w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ // One black pawn is missing (promoted), but we see an extra white queen
+ expect(captured.white).toEqual(['p']);
+ expect(captured.black).toEqual([]);
+ });
+
+ it('should handle custom starting position', () => {
+ const customStart = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQ - 0 1';
+ const currentFen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R3K3 w Q - 0 1';
+ const captured = getCapturedPieces(customStart, currentFen);
+ expect(captured.white).toEqual([]);
+ expect(captured.black).toEqual(['r']);
+ });
+
+ it('should preserve order based on piece type iteration', () => {
+ // Multiple pieces of same type should be counted correctly
+ const currentFen = 'rnbqkb1r/ppppppp1/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ const captured = getCapturedPieces(startFen, currentFen);
+ expect(captured.white).toEqual(['p', 'n']);
+ });
+ });
+});
diff --git a/src/utils/elo.test.ts b/src/utils/elo.test.ts
new file mode 100644
index 0000000..76a2365
--- /dev/null
+++ b/src/utils/elo.test.ts
@@ -0,0 +1,96 @@
+import { describe, it, expect } from 'vitest';
+import { expectedScore, calculateNewRating, resultToScore } from './elo';
+
+describe('elo', () => {
+ describe('expectedScore', () => {
+ it('should return 0.5 for equal ratings', () => {
+ expect(expectedScore(1500, 1500)).toBe(0.5);
+ });
+
+ it('should return higher than 0.5 when player rating is higher', () => {
+ expect(expectedScore(1600, 1500)).toBeGreaterThan(0.5);
+ });
+
+ it('should return lower than 0.5 when player rating is lower', () => {
+ expect(expectedScore(1400, 1500)).toBeLessThan(0.5);
+ });
+
+ it('should return approximately 0.76 for 200 point difference', () => {
+ const result = expectedScore(1700, 1500);
+ expect(result).toBeCloseTo(0.76, 2);
+ });
+
+ it('should return approximately 0.91 for 400 point difference', () => {
+ const result = expectedScore(1900, 1500);
+ expect(result).toBeCloseTo(0.91, 2);
+ });
+ });
+
+ describe('calculateNewRating', () => {
+ it('should return same rating when result matches expectation', () => {
+ const newRating = calculateNewRating(1500, 1500, 0.5, 20);
+ expect(newRating).toBe(1500);
+ });
+
+ it('should increase rating when player wins against equal opponent', () => {
+ const newRating = calculateNewRating(1500, 1500, 1, 20);
+ expect(newRating).toBeGreaterThan(1500);
+ expect(newRating).toBe(1510);
+ });
+
+ it('should decrease rating when player loses against equal opponent', () => {
+ const newRating = calculateNewRating(1500, 1500, 0, 20);
+ expect(newRating).toBeLessThan(1500);
+ expect(newRating).toBe(1490);
+ });
+
+ it('should handle upset victories with larger rating changes', () => {
+ const newRating = calculateNewRating(1400, 1600, 1, 20);
+ expect(newRating).toBeGreaterThan(1400);
+ // Winning against higher rated opponent should give more points
+ expect(newRating).toBeGreaterThan(1410);
+ });
+
+ it('should handle expected victories with smaller rating changes', () => {
+ const newRating = calculateNewRating(1600, 1400, 1, 20);
+ expect(newRating).toBeGreaterThan(1600);
+ // Winning against lower rated opponent should give fewer points
+ expect(newRating).toBeLessThan(1610);
+ });
+
+ it('should respect custom K-factor', () => {
+ const newRating1 = calculateNewRating(1500, 1500, 1, 10);
+ const newRating2 = calculateNewRating(1500, 1500, 1, 40);
+ expect(newRating1).toBe(1505); // K=10: smaller change
+ expect(newRating2).toBe(1520); // K=40: larger change
+ });
+
+ it('should return rounded integer values', () => {
+ const newRating = calculateNewRating(1500, 1550, 0.5, 32);
+ expect(Number.isInteger(newRating)).toBe(true);
+ });
+ });
+
+ describe('resultToScore', () => {
+ it('should return 1 when white wins and player is white', () => {
+ expect(resultToScore('1-0', true)).toBe(1);
+ });
+
+ it('should return 0 when white wins and player is black', () => {
+ expect(resultToScore('1-0', false)).toBe(0);
+ });
+
+ it('should return 0 when black wins and player is white', () => {
+ expect(resultToScore('0-1', true)).toBe(0);
+ });
+
+ it('should return 1 when black wins and player is black', () => {
+ expect(resultToScore('0-1', false)).toBe(1);
+ });
+
+ it('should return 0.5 for draw regardless of color', () => {
+ expect(resultToScore('1/2-1/2', true)).toBe(0.5);
+ expect(resultToScore('1/2-1/2', false)).toBe(0.5);
+ });
+ });
+});
diff --git a/src/utils/elo.ts b/src/utils/elo.ts
index 63a0e45..e3b9668 100644
--- a/src/utils/elo.ts
+++ b/src/utils/elo.ts
@@ -1,7 +1,35 @@
+/**
+ * Calculate the expected score for a player based on ELO ratings
+ * Uses the standard ELO formula: E = 1 / (1 + 10^((R_opponent - R_player) / 400))
+ *
+ * @param rating - The player's current ELO rating
+ * @param opponentRating - The opponent's current ELO rating
+ * @returns Expected score between 0 and 1 (0.5 means equal chance)
+ *
+ * @example
+ * expectedScore(1500, 1500) // Returns 0.5 (50% expected)
+ * expectedScore(1600, 1400) // Returns ~0.76 (76% expected to win)
+ */
export function expectedScore(rating: number, opponentRating: number): number {
return 1 / (1 + Math.pow(10, (opponentRating - rating) / 400));
}
+/**
+ * Calculate new ELO rating after a game
+ * Uses the formula: R_new = R_old + K * (S - E)
+ * where S is the actual score and E is the expected score
+ *
+ * @param rating - Player's current ELO rating
+ * @param opponentRating - Opponent's current ELO rating
+ * @param score - Actual game score (1 for win, 0.5 for draw, 0 for loss)
+ * @param k - K-factor (default: 20) - higher values mean larger rating changes
+ * @returns New ELO rating (rounded to nearest integer)
+ *
+ * @example
+ * calculateNewRating(1500, 1500, 1) // Returns 1510 (won against equal opponent)
+ * calculateNewRating(1500, 1500, 0) // Returns 1490 (lost against equal opponent)
+ * calculateNewRating(1500, 1500, 0.5) // Returns 1500 (draw against equal opponent)
+ */
export function calculateNewRating(
rating: number,
opponentRating: number,
@@ -12,6 +40,18 @@ export function calculateNewRating(
return Math.round(rating + k * (score - exp));
}
+/**
+ * Convert chess game result to numerical score
+ *
+ * @param result - Game result in standard chess notation ('1-0', '0-1', or '1/2-1/2')
+ * @param isWhite - Whether the player was playing as white
+ * @returns Score value: 1 for win, 0.5 for draw, 0 for loss
+ *
+ * @example
+ * resultToScore('1-0', true) // Returns 1 (white won)
+ * resultToScore('1-0', false) // Returns 0 (black lost)
+ * resultToScore('1/2-1/2', true) // Returns 0.5 (draw)
+ */
export function resultToScore(result: '1-0' | '0-1' | '1/2-1/2', isWhite: boolean): number {
if (result === '1/2-1/2') return 0.5;
if ((result === '1-0' && isWhite) || (result === '0-1' && !isWhite)) return 1;
diff --git a/src/utils/exportFormats.ts b/src/utils/exportFormats.ts
new file mode 100644
index 0000000..314be69
--- /dev/null
+++ b/src/utils/exportFormats.ts
@@ -0,0 +1,258 @@
+import type { ChessGame } from '../data/masterGames';
+
+/**
+ * Export games to JSON format
+ * @param games - Array of chess games to export
+ * @param filename - Name of the file to download
+ */
+export function exportToJSON(games: ChessGame[], filename = 'chess-games.json'): void {
+ try {
+ const jsonData = JSON.stringify(games, null, 2);
+ const blob = new Blob([jsonData], { type: 'application/json' });
+ downloadFile(blob, filename);
+ } catch (error) {
+ console.error('Error exporting to JSON:', error);
+ throw new Error('Failed to export games to JSON format');
+ }
+}
+
+/**
+ * Export games to CSV format
+ * @param games - Array of chess games to export
+ * @param filename - Name of the file to download
+ */
+export function exportToCSV(games: ChessGame[], filename = 'chess-games.csv'): void {
+ try {
+ if (games.length === 0) {
+ throw new Error('No games to export');
+ }
+
+ // CSV Headers
+ const headers = [
+ 'ID',
+ 'White',
+ 'Black',
+ 'Event',
+ 'Opening',
+ 'Result',
+ 'Year',
+ 'Moves',
+ 'Description',
+ ];
+
+ // Convert games to CSV rows
+ const rows = games.map(game => [
+ game.id,
+ escapeCsvValue(game.white),
+ escapeCsvValue(game.black),
+ escapeCsvValue(game.event),
+ escapeCsvValue(game.opening),
+ game.result,
+ game.year,
+ escapeCsvValue(game.moves),
+ escapeCsvValue(game.description || ''),
+ ]);
+
+ // Combine headers and rows
+ const csvContent = [headers.join(','), ...rows.map(row => row.join(','))].join('\n');
+
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+ downloadFile(blob, filename);
+ } catch (error) {
+ console.error('Error exporting to CSV:', error);
+ throw new Error('Failed to export games to CSV format');
+ }
+}
+
+/**
+ * Escape CSV values containing special characters
+ * @param value - The value to escape
+ * @returns Escaped CSV value
+ */
+function escapeCsvValue(value: string | number): string {
+ const stringValue = String(value);
+
+ // If value contains comma, quotes, or newlines, wrap in quotes and escape existing quotes
+ if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
+ return `"${stringValue.replace(/"/g, '""')}"`;
+ }
+
+ return stringValue;
+}
+
+/**
+ * Export games to Excel-compatible format (TSV)
+ * @param games - Array of chess games to export
+ * @param filename - Name of the file to download
+ */
+export function exportToExcel(games: ChessGame[], filename = 'chess-games.xlsx'): void {
+ try {
+ if (games.length === 0) {
+ throw new Error('No games to export');
+ }
+
+ // Use tab-separated values for better Excel compatibility
+ const headers = [
+ 'ID',
+ 'White',
+ 'Black',
+ 'Event',
+ 'Opening',
+ 'Result',
+ 'Year',
+ 'Moves',
+ 'Description',
+ ];
+
+ const rows = games.map(game => [
+ game.id,
+ game.white,
+ game.black,
+ game.event,
+ game.opening,
+ game.result,
+ game.year,
+ game.moves,
+ game.description || '',
+ ]);
+
+ const tsvContent = [headers.join('\t'), ...rows.map(row => row.join('\t'))].join('\n');
+
+ // Add BOM for proper UTF-8 encoding in Excel
+ const BOM = '\uFEFF';
+ const blob = new Blob([BOM + tsvContent], { type: 'application/vnd.ms-excel;charset=utf-8;' });
+ downloadFile(blob, filename.replace('.xlsx', '.xls'));
+ } catch (error) {
+ console.error('Error exporting to Excel:', error);
+ throw new Error('Failed to export games to Excel format');
+ }
+}
+
+/**
+ * Export a single game to JSON
+ * @param game - Chess game to export
+ * @param filename - Name of the file to download
+ */
+export function exportGameToJSON(game: ChessGame, filename?: string): void {
+ const name = filename || `chess-game-${game.id}.json`;
+ exportToJSON([game], name);
+}
+
+/**
+ * Download a file from a Blob
+ * @param blob - The blob to download
+ * @param filename - Name of the file
+ */
+function downloadFile(blob: Blob, filename: string): void {
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+}
+
+/**
+ * Parse CSV file and import games
+ * @param csvContent - The CSV content as string
+ * @returns Array of parsed games
+ */
+export function importFromCSV(csvContent: string): ChessGame[] {
+ const lines = csvContent.split('\n').filter(line => line.trim());
+
+ if (lines.length < 2) {
+ throw new Error('CSV file is empty or invalid');
+ }
+
+ // Skip header row
+ const dataLines = lines.slice(1);
+
+ const games: ChessGame[] = dataLines.map((line, index) => {
+ const values = parseCSVLine(line);
+
+ if (values.length < 8) {
+ throw new Error(`Invalid CSV format at line ${index + 2}`);
+ }
+
+ return {
+ id: parseInt(values[0], 10) || index + 1,
+ white: values[1],
+ black: values[2],
+ event: values[3],
+ opening: values[4],
+ result: values[5] as '1-0' | '0-1' | '1/2-1/2',
+ year: parseInt(values[6], 10),
+ moves: values[7],
+ description: values[8] || '',
+ };
+ });
+
+ return games;
+}
+
+/**
+ * Parse a single CSV line handling quoted values
+ * @param line - CSV line to parse
+ * @returns Array of values
+ */
+function parseCSVLine(line: string): string[] {
+ const values: string[] = [];
+ let currentValue = '';
+ let insideQuotes = false;
+
+ for (let i = 0; i < line.length; i++) {
+ const char = line[i];
+ const nextChar = line[i + 1];
+
+ if (char === '"') {
+ if (insideQuotes && nextChar === '"') {
+ // Escaped quote
+ currentValue += '"';
+ i++; // Skip next quote
+ } else {
+ // Toggle quote state
+ insideQuotes = !insideQuotes;
+ }
+ } else if (char === ',' && !insideQuotes) {
+ // End of value
+ values.push(currentValue);
+ currentValue = '';
+ } else {
+ currentValue += char;
+ }
+ }
+
+ // Add last value
+ values.push(currentValue);
+
+ return values;
+}
+
+/**
+ * Import games from JSON file
+ * @param jsonContent - The JSON content as string
+ * @returns Array of parsed games
+ */
+export function importFromJSON(jsonContent: string): ChessGame[] {
+ try {
+ const games = JSON.parse(jsonContent);
+
+ if (!Array.isArray(games)) {
+ throw new Error('JSON file must contain an array of games');
+ }
+
+ // Validate game structure
+ games.forEach((game, index) => {
+ if (!game.white || !game.black || !game.moves) {
+ throw new Error(`Invalid game structure at index ${index}`);
+ }
+ });
+
+ return games;
+ } catch (error) {
+ console.error('Error parsing JSON:', error);
+ throw new Error('Failed to import games from JSON format');
+ }
+}
diff --git a/src/utils/fenUtils.ts b/src/utils/fenUtils.ts
new file mode 100644
index 0000000..e56982c
--- /dev/null
+++ b/src/utils/fenUtils.ts
@@ -0,0 +1,227 @@
+import { Chess } from 'chess.js';
+
+/**
+ * Validate a FEN string
+ * @param fen - The FEN string to validate
+ * @returns true if valid, false otherwise
+ */
+export function isValidFEN(fen: string): boolean {
+ try {
+ const chess = new Chess(fen);
+ return chess.fen() === fen;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Get the starting position FEN
+ * @returns Standard chess starting position in FEN notation
+ */
+export function getStartingFEN(): string {
+ return 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+}
+
+/**
+ * Export current position to FEN string
+ * @param chess - Chess.js instance
+ * @returns FEN string of current position
+ */
+export function exportToFEN(chess: Chess): string {
+ return chess.fen();
+}
+
+/**
+ * Import a position from FEN string
+ * @param fen - The FEN string to import
+ * @returns Chess.js instance with the position loaded
+ * @throws Error if FEN is invalid
+ */
+export function importFromFEN(fen: string): Chess {
+ if (!isValidFEN(fen)) {
+ throw new Error('Invalid FEN notation');
+ }
+
+ return new Chess(fen);
+}
+
+/**
+ * Copy FEN to clipboard
+ * @param fen - The FEN string to copy
+ * @returns Promise that resolves when copied
+ */
+export async function copyFENToClipboard(fen: string): Promise {
+ try {
+ if (navigator.clipboard && window.isSecureContext) {
+ await navigator.clipboard.writeText(fen);
+ } else {
+ // Fallback for older browsers
+ const textArea = document.createElement('textarea');
+ textArea.value = fen;
+ textArea.style.position = 'fixed';
+ textArea.style.left = '-999999px';
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand('copy');
+ document.body.removeChild(textArea);
+ }
+ } catch (error) {
+ console.error('Failed to copy FEN to clipboard:', error);
+ throw new Error('Failed to copy to clipboard');
+ }
+}
+
+/**
+ * Download FEN as a text file
+ * @param fen - The FEN string to download
+ * @param filename - Name of the file (default: position.fen)
+ */
+export function downloadFEN(fen: string, filename = 'position.fen'): void {
+ const blob = new Blob([fen], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+}
+
+/**
+ * Parse FEN to get position information
+ * @param fen - The FEN string to parse
+ * @returns Object with position details
+ */
+export interface FENInfo {
+ pieces: string; // Piece placement
+ activeColor: 'w' | 'b'; // Active color
+ castling: string; // Castling availability
+ enPassant: string; // En passant target square
+ halfmove: number; // Halfmove clock
+ fullmove: number; // Fullmove number
+}
+
+export function parseFEN(fen: string): FENInfo {
+ if (!isValidFEN(fen)) {
+ throw new Error('Invalid FEN notation');
+ }
+
+ const parts = fen.split(' ');
+
+ return {
+ pieces: parts[0],
+ activeColor: parts[1] as 'w' | 'b',
+ castling: parts[2],
+ enPassant: parts[3],
+ halfmove: parseInt(parts[4], 10),
+ fullmove: parseInt(parts[5], 10),
+ };
+}
+
+/**
+ * Build FEN from components
+ * @param info - FEN information object
+ * @returns Complete FEN string
+ */
+export function buildFEN(info: FENInfo): string {
+ return `${info.pieces} ${info.activeColor} ${info.castling} ${info.enPassant} ${info.halfmove} ${info.fullmove}`;
+}
+
+/**
+ * Get a simplified FEN (without move counters)
+ * Useful for position comparison
+ * @param fen - The complete FEN string
+ * @returns Simplified FEN (first 4 fields only)
+ */
+export function getSimplifiedFEN(fen: string): string {
+ const parts = fen.split(' ');
+ return parts.slice(0, 4).join(' ');
+}
+
+/**
+ * Check if two positions are the same (ignoring move counters)
+ * @param fen1 - First FEN string
+ * @param fen2 - Second FEN string
+ * @returns true if positions are identical
+ */
+export function arePositionsEqual(fen1: string, fen2: string): boolean {
+ return getSimplifiedFEN(fen1) === getSimplifiedFEN(fen2);
+}
+
+/**
+ * Get human-readable description of a position
+ * @param fen - The FEN string
+ * @returns Description object
+ */
+export interface PositionDescription {
+ toMove: string;
+ castlingRights: string[];
+ enPassantSquare: string | null;
+ moveNumber: number;
+}
+
+export function describePosition(fen: string): PositionDescription {
+ const info = parseFEN(fen);
+
+ const castlingRights: string[] = [];
+ if (info.castling.includes('K')) castlingRights.push('White kingside');
+ if (info.castling.includes('Q')) castlingRights.push('White queenside');
+ if (info.castling.includes('k')) castlingRights.push('Black kingside');
+ if (info.castling.includes('q')) castlingRights.push('Black queenside');
+
+ return {
+ toMove: info.activeColor === 'w' ? 'White' : 'Black',
+ castlingRights: castlingRights.length > 0 ? castlingRights : ['None'],
+ enPassantSquare: info.enPassant !== '-' ? info.enPassant : null,
+ moveNumber: info.fullmove,
+ };
+}
+
+/**
+ * Load FEN from a file
+ * @param file - The file to read
+ * @returns Promise with the FEN string
+ */
+export function loadFENFromFile(file: File): Promise {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = e => {
+ const content = e.target?.result as string;
+ const fen = content.trim();
+
+ if (!isValidFEN(fen)) {
+ reject(new Error('File does not contain a valid FEN notation'));
+ return;
+ }
+
+ resolve(fen);
+ };
+
+ reader.onerror = () => {
+ reject(new Error('Failed to read file'));
+ };
+
+ reader.readAsText(file);
+ });
+}
+
+/**
+ * Generate a random legal position
+ * @returns Chess.js instance with a random position
+ */
+export function generateRandomPosition(): Chess {
+ const chess = new Chess();
+ const moves = Math.floor(Math.random() * 20) + 5; // 5-25 random moves
+
+ for (let i = 0; i < moves; i++) {
+ const possibleMoves = chess.moves();
+ if (possibleMoves.length === 0) break;
+
+ const randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
+ chess.move(randomMove);
+ }
+
+ return chess;
+}
diff --git a/src/utils/materialEvaluation.test.ts b/src/utils/materialEvaluation.test.ts
new file mode 100644
index 0000000..b2f834c
--- /dev/null
+++ b/src/utils/materialEvaluation.test.ts
@@ -0,0 +1,81 @@
+import { describe, it, expect } from 'vitest';
+import { Chess } from 'chess.js';
+import { materialEvaluation } from './materialEvaluation';
+
+describe('materialEvaluation', () => {
+ it('should return 0 for starting position', () => {
+ const startFen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ expect(materialEvaluation(startFen)).toBe(0);
+ });
+
+ it('should work with Chess instance', () => {
+ const chess = new Chess();
+ expect(materialEvaluation(chess)).toBe(0);
+ });
+
+ it('should return positive value when white is ahead', () => {
+ // White has captured a black pawn
+ const fen = 'rnbqkbnr/ppp1pppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
+ expect(materialEvaluation(fen)).toBeGreaterThan(0);
+ });
+
+ it('should return negative value when black is ahead', () => {
+ // Black has captured a white pawn
+ const fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1';
+ expect(materialEvaluation(fen)).toBeLessThan(0);
+ });
+
+ it('should correctly evaluate queen vs pawns', () => {
+ // Only white queen and black pawns
+ const fen = '4k3/pppppppp/8/8/8/8/8/4K2Q w - - 0 1';
+ const evaluation = materialEvaluation(fen);
+ // Queen is 9 points, 8 pawns is 8 points, so white should be +1
+ expect(evaluation).toBe(1);
+ });
+
+ it('should correctly evaluate rooks vs knights and bishops', () => {
+ // White has 2 rooks (10 points), black has 2 knights and 2 bishops (12 points)
+ const fen = 'rnb1kbnr/8/8/8/8/8/8/R3K2R w - - 0 1';
+ const evaluation = materialEvaluation(fen);
+ // White: 2 rooks = 10, Black: 2n + 2b = 12, difference = -2
+ expect(evaluation).toBe(-2);
+ });
+
+ it('should give kings value of 0', () => {
+ // Only kings
+ const fen = '4k3/8/8/8/8/8/8/4K3 w - - 0 1';
+ expect(materialEvaluation(fen)).toBe(0);
+ });
+
+ it('should handle empty squares correctly', () => {
+ // Position with many empty squares
+ const fen = '4k3/8/8/3Q4/8/8/8/4K3 w - - 0 1';
+ const evaluation = materialEvaluation(fen);
+ expect(evaluation).toBe(9); // Just the white queen
+ });
+
+ it('should correctly count piece values', () => {
+ // Known position: 1 pawn = 1, 1 knight = 3, 1 bishop = 3, 1 rook = 5, 1 queen = 9
+ const fen = '4k3/8/8/8/8/8/P7/RNBQK3 w - - 0 1';
+ const evaluation = materialEvaluation(fen);
+ // 1 + 5 + 3 + 9 = 18
+ expect(evaluation).toBe(18);
+ });
+
+ it('should handle complex middlegame positions', () => {
+ // After some typical exchanges
+ const fen = 'r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/3P1N2/PPP2PPP/RNBQK2R w KQkq - 0 1';
+ const evaluation = materialEvaluation(fen);
+ // Material should be roughly equal
+ expect(evaluation).toBeGreaterThanOrEqual(-1);
+ expect(evaluation).toBeLessThanOrEqual(1);
+ });
+
+ it('should handle endgame with unbalanced material', () => {
+ // White has rook and pawn, black has knight and bishop
+ const fen = '4k3/8/8/8/8/8/P7/R3Knb1 w - - 0 1';
+ const evaluation = materialEvaluation(fen);
+ // White: R(5) + P(1) = 6, Black: N(3) + B(3) = 6
+ expect(evaluation).toBe(0);
+ });
+});
diff --git a/src/utils/materialEvaluation.ts b/src/utils/materialEvaluation.ts
index 94b57ac..2505ab5 100644
--- a/src/utils/materialEvaluation.ts
+++ b/src/utils/materialEvaluation.ts
@@ -1,7 +1,36 @@
import { Chess } from 'chess.js';
+/**
+ * Standard piece values in pawns
+ * - Pawn: 1
+ * - Knight: 3
+ * - Bishop: 3
+ * - Rook: 5
+ * - Queen: 9
+ * - King: 0 (infinite value in gameplay, 0 for material calculation)
+ */
const VALUE: Record = { p: 1, n: 3, b: 3, r: 5, q: 9, k: 0 };
+/**
+ * Calculate material evaluation of a chess position
+ * Counts the total material value difference between white and black
+ *
+ * @param input - Either a FEN string or a Chess.js instance
+ * @returns Material balance in pawns (positive = white ahead, negative = black ahead, 0 = equal)
+ *
+ * @example
+ * // Starting position (equal material)
+ * materialEvaluation('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1') // Returns 0
+ *
+ * @example
+ * // White is up a queen (9 points)
+ * materialEvaluation('4k3/8/8/8/8/8/8/4K2Q w - - 0 1') // Returns 9
+ *
+ * @example
+ * // Using Chess.js instance
+ * const chess = new Chess();
+ * materialEvaluation(chess) // Returns 0
+ */
export function materialEvaluation(input: string | Chess): number {
const chess = typeof input === 'string' ? new Chess(input) : input;
return chess
diff --git a/src/utils/tournament.test.ts b/src/utils/tournament.test.ts
new file mode 100644
index 0000000..cc5f4b7
--- /dev/null
+++ b/src/utils/tournament.test.ts
@@ -0,0 +1,258 @@
+import { describe, it, expect } from 'vitest';
+import { generateRoundRobin, calculateStandings, Tournament } from './tournament';
+
+describe('tournament', () => {
+ describe('generateRoundRobin', () => {
+ it('should generate correct number of rounds for even number of players', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const rounds = generateRoundRobin(players);
+ // For n players, there should be n-1 rounds
+ expect(rounds).toHaveLength(3);
+ });
+
+ it('should generate correct number of rounds for odd number of players', () => {
+ const players = ['A', 'B', 'C', 'D', 'E'];
+ const rounds = generateRoundRobin(players);
+ // With BYE added, 6 players -> 5 rounds
+ expect(rounds).toHaveLength(5);
+ });
+
+ it('should ensure each pairing has result field initialized', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const rounds = generateRoundRobin(players);
+ rounds.forEach(round => {
+ round.pairings.forEach(pairing => {
+ expect(pairing.result).toBe('');
+ expect(pairing.white).toBeTruthy();
+ expect(pairing.black).toBeTruthy();
+ });
+ });
+ });
+
+ it('should not include BYE in pairings', () => {
+ const players = ['A', 'B', 'C'];
+ const rounds = generateRoundRobin(players);
+ rounds.forEach(round => {
+ round.pairings.forEach(pairing => {
+ expect(pairing.white).not.toBe('__BYE__');
+ expect(pairing.black).not.toBe('__BYE__');
+ });
+ });
+ });
+
+ it('should alternate colors correctly', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const rounds = generateRoundRobin(players);
+
+ // Track color assignments for each player
+ const whiteGames: Record = { A: 0, B: 0, C: 0, D: 0 };
+ const blackGames: Record = { A: 0, B: 0, C: 0, D: 0 };
+
+ rounds.forEach(round => {
+ round.pairings.forEach(pairing => {
+ whiteGames[pairing.white]++;
+ blackGames[pairing.black]++;
+ });
+ });
+
+ // Each player should play roughly equal games as white and black
+ players.forEach(player => {
+ expect(Math.abs(whiteGames[player] - blackGames[player])).toBeLessThanOrEqual(1);
+ });
+ });
+
+ it('should ensure each player plays every other player exactly once', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const rounds = generateRoundRobin(players);
+ const matchups: Record> = {
+ A: new Set(),
+ B: new Set(),
+ C: new Set(),
+ D: new Set(),
+ };
+
+ rounds.forEach(round => {
+ round.pairings.forEach(pairing => {
+ matchups[pairing.white].add(pairing.black);
+ matchups[pairing.black].add(pairing.white);
+ });
+ });
+
+ // Each player should have played against all others
+ players.forEach(player => {
+ expect(matchups[player].size).toBe(players.length - 1);
+ });
+ });
+
+ it('should have correct number of games per round for even players', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const rounds = generateRoundRobin(players);
+ // With 4 players, each round should have 2 games
+ rounds.forEach(round => {
+ expect(round.pairings).toHaveLength(2);
+ });
+ });
+
+ it('should have correct number of games per round for odd players', () => {
+ const players = ['A', 'B', 'C', 'D', 'E'];
+ const rounds = generateRoundRobin(players);
+ // With 5 players (+ BYE = 6), each round should have 2 games (one player gets BYE)
+ rounds.forEach(round => {
+ expect(round.pairings).toHaveLength(2);
+ });
+ });
+
+ it('should handle 2 players', () => {
+ const players = ['A', 'B'];
+ const rounds = generateRoundRobin(players);
+ expect(rounds).toHaveLength(1);
+ expect(rounds[0].pairings).toHaveLength(1);
+ expect(rounds[0].pairings[0].white).toBe('A');
+ expect(rounds[0].pairings[0].black).toBe('B');
+ });
+
+ it('should not mutate the original players array', () => {
+ const players = ['A', 'B', 'C', 'D'];
+ const originalPlayers = [...players];
+ generateRoundRobin(players);
+ expect(players).toEqual(originalPlayers);
+ });
+ });
+
+ describe('calculateStandings', () => {
+ it('should return 0 points for all players with no results', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B', 'C', 'D'],
+ rounds: generateRoundRobin(['A', 'B', 'C', 'D']),
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings).toEqual({ A: 0, B: 0, C: 0, D: 0 });
+ });
+
+ it('should correctly calculate wins', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B'],
+ rounds: [
+ {
+ pairings: [{ white: 'A', black: 'B', result: '1-0' }],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(1);
+ expect(standings.B).toBe(0);
+ });
+
+ it('should correctly calculate losses', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B'],
+ rounds: [
+ {
+ pairings: [{ white: 'A', black: 'B', result: '0-1' }],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(0);
+ expect(standings.B).toBe(1);
+ });
+
+ it('should correctly calculate draws', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B'],
+ rounds: [
+ {
+ pairings: [{ white: 'A', black: 'B', result: '1/2-1/2' }],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(0.5);
+ expect(standings.B).toBe(0.5);
+ });
+
+ it('should accumulate points across multiple rounds', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B', 'C'],
+ rounds: [
+ {
+ pairings: [
+ { white: 'A', black: 'B', result: '1-0' },
+ { white: 'C', black: 'A', result: '0-1' },
+ ],
+ },
+ {
+ pairings: [{ white: 'B', black: 'C', result: '1/2-1/2' }],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(2); // 2 wins
+ expect(standings.B).toBe(0.5); // 1 loss, 1 draw
+ expect(standings.C).toBe(0.5); // 1 loss, 1 draw
+ });
+
+ it('should handle empty results', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B', 'C'],
+ rounds: [
+ {
+ pairings: [
+ { white: 'A', black: 'B', result: '' },
+ { white: 'B', black: 'C', result: '1-0' },
+ ],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(0);
+ expect(standings.B).toBe(1);
+ expect(standings.C).toBe(0);
+ });
+
+ it('should handle complex tournament with multiple players', () => {
+ const tournament: Tournament = {
+ id: 't1',
+ name: 'Test Tournament',
+ players: ['A', 'B', 'C', 'D'],
+ rounds: [
+ {
+ pairings: [
+ { white: 'A', black: 'B', result: '1-0' },
+ { white: 'C', black: 'D', result: '1/2-1/2' },
+ ],
+ },
+ {
+ pairings: [
+ { white: 'A', black: 'C', result: '0-1' },
+ { white: 'B', black: 'D', result: '1-0' },
+ ],
+ },
+ {
+ pairings: [
+ { white: 'A', black: 'D', result: '1/2-1/2' },
+ { white: 'B', black: 'C', result: '0-1' },
+ ],
+ },
+ ],
+ };
+ const standings = calculateStandings(tournament);
+ expect(standings.A).toBe(1.5); // 1W, 1L, 1D
+ expect(standings.B).toBe(1); // 1W, 1L, 1L
+ expect(standings.C).toBe(2.5); // 2W, 0L, 1D
+ expect(standings.D).toBe(1); // 0W, 1L, 2D
+ });
+ });
+});
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..c5badf3
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './src/test/setup.ts',
+ css: true,
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'src/test/',
+ '**/*.spec.ts',
+ '**/*.test.ts',
+ '**/*.spec.tsx',
+ '**/*.test.tsx',
+ ],
+ },
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+});