sokko-g/docs/STORAGE_SCHEMA.md
Shinuwa d0e1fd73b1
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
Defer media loading and strengthen persistence tests
2026-09-12 23:28:05 +02:00

38 KiB

Storage Schema

Ce document décrit les données persistées dans IndexedDB pour les toolboxes. Il doit être mis à jour à chaque ajout ou modification d'outil.

IndexedDB

Base : sokkog

Object stores :

  • kv : données globales simples.
  • modules : données des outils, séparées par toolbox et par module.

Clés principales dans kv :

{
  "toolboxes": [],
  "links": {},
  "setting:toolboxOrder": [],
  "setting:storageBudgetBytes": 536870912,
  "setting:toastPosition": "right"
}

Entrée type dans modules :

{
  "key": "tbx1:mod1",
  "data": {}
}

Toolbox

Ordre et confirmation des écritures

Le démarrage attend la lecture du registre, des liens, des modules hors médias et de la position des notifications avant d'ouvrir l'interface. Un échec laisse le stockage non prêt : les méthodes d'écriture refusent les opérations, et la création, l'import et l'export ne peuvent pas utiliser un état vide incomplet. Le bouton de reprise relance uniquement les lectures, sans supprimer de données. Une ouverture IndexedDB échouée n'est pas conservée dans le cache de connexion. Les erreurs de lecture sont distinctes des erreurs d'écriture et ne sont pas présentées systématiquement comme un dépassement de quota.

Dans une instance de l'application, le registre, les liens, les modules et la position des notifications partagent une file d'écritures. L'interface présente les modifications en attente ; en cas d'échec, elle est reconstruite depuis le dernier état confirmé, puis les opérations restantes sont réappliquées.

La création, l'association, l'import et la suppression d'une toolbox utilisent une transaction commune aux stores kv et modules. Les imports ajoutent leur contenu à l'état courant au moment de l'exécution et annoncent leur succès après la fin de transaction. Une erreur synchrone pendant sa préparation déclenche l'abandon de la transaction. Les écritures ordinaires d'un module restent ciblées sur sa clé et ne réécrivent pas les images des autres outils.

Les données des outils images et imageAnnotation sont lues à leur première ouverture, en page complète ou dans le panneau latéral. Les minuteurs et calendriers restent chargés au démarrage pour assurer les alertes globales. Les couvertures, incluses dans le registre des toolboxes, restent également chargées au démarrage. Un marqueur uniquement en mémoire distingue un média non lu d'un outil vide ; il n'est jamais persisté ni exporté. Les transactions de snapshot conservent les entrées non lues, sauf celles explicitement supprimées de l'état courant. Une lecture tardive ne réintroduit pas un outil supprimé entre-temps.

Pendant la lecture, le contenu de l'outil est bloqué. Un échec affiche une reprise locale sans empêcher les autres outils de fonctionner ni autoriser l'écrasement du média non lu. Après ouverture, les médias restent en mémoire jusqu'au rechargement de l'application ; il n'y a pas d'éviction à la fermeture.

Les exports attendent les écritures précédentes dans la même file et lisent les médias manquants depuis l'état confirmé, sans les ajouter au cache de l'interface. Si cette lecture échoue, aucun fichier partiel n'est téléchargé. Les formats IndexedDB et import/export sont inchangés.

Cette coordination est locale à l'instance React ; elle ne synchronise pas l'édition entre plusieurs onglets.

Les estimations de quota demandées après sauvegarde sont regroupées sur une fenêtre de 250 ms, avec une seule estimation active. Une écriture intervenant pendant le calcul déclenche une nouvelle estimation. Les sauvegardes ne sont pas retardées ; en cas d'estimation indisponible, le dernier quota connu est conservé.

Les toolboxes sont stockées dans kv/toolboxes.

{
  "id": "tbx1",
  "name": "Monster Hunter Wilds",
  "gameId": "mhwilds",
  "icon": "/static/img/toolbox-icons/toolbox.png",
  "customCover": "data:image/webp;base64,...",
  "modules": [
    {
      "id": "mod1",
      "type": "checklist",
      "title": "Armures"
    }
  ],
  "moduleColumns": 2,
  "moduleOrder": {
    "one": ["mod1"],
    "two": [["mod1"], []]
  },
  "tutorial": true,
  "updatedAt": "2026-07-25T12:00:00.000Z"
}

Notes :

  • gameId est vide pour une toolbox libre.
  • icon est utilisé uniquement pour les toolboxes libres.
  • customCover est une image rectangulaire optionnelle utilisée comme cover de card pour les toolboxes libres. Elle est stockée en data URL image après recadrage, sans conserver le fichier original.
  • moduleColumns vaut 1 ou 2 et contrôle l'affichage de la page toolbox complète ; le panneau latéral utilise toujours l'ordre une colonne.
  • moduleOrder.one stocke l'ordre du mode une colonne.
  • moduleOrder.two stocke l'ordre explicite des colonnes gauche et droite en mode deux colonnes.
  • Chaque identifiant d'outil présent dans modules doit apparaître exactement une fois dans moduleOrder.one et exactement une fois dans l'une des deux colonnes de moduleOrder.two.
  • Si une ancienne toolbox n'a pas encore moduleOrder.two, les outils sont répartis automatiquement depuis l'ordre une colonne avec une moitié en colonne gauche et une moitié en colonne droite.
  • Les données lourdes des outils ne sont pas stockées dans la toolbox, mais dans modules.
  • tutorial est un indicateur temporaire réservé aux toolboxes créées depuis la prise en main. Il déclenche les premiers repères à l'ouverture, puis il est retiré automatiquement du stockage.

Liens Jeu / Toolbox

Les associations entre pages jeux et toolboxes sont stockées dans kv/links.

{
  "mhwilds": "tbx1",
  "diablo4": "tbx2"
}

Outil Bloc Note

Type : notepad

{
  "activePageId": "page1",
  "pages": [
    {
      "id": "page1",
      "title": "Page 1",
      "html": "<p>Notes <strong>rapides</strong>...</p>",
      "text": "Notes rapides...",
      "updatedAt": "2026-07-29T12:00:00.000Z",
      "drawingMode": "permanent",
      "drawings": {
        "strokes": [
          {
            "id": "stroke1",
            "color": "#f6c453",
            "width": 4,
            "points": [
              { "x": 12.5, "y": 48 },
              { "x": 24, "y": 52.5 }
            ]
          }
        ]
      }
    }
  ]
}

Stockage compact :

  • l'ancien format mono-page { "html": "...", "text": "..." } reste accepté et est converti en pages[0] au prochain enregistrement ;
  • activePageId est omis si la première page stockée est active ;
  • html est nettoyé avec une allowlist de balises et de couleurs ;
  • text conserve une version texte pour les exports et les fallbacks ;
  • updatedAt est omis si vide ou invalide ;
  • drawingMode vaut temporary par défaut et est omis dans ce cas ;
  • drawings est stocké uniquement si drawingMode vaut permanent et qu'au moins un trait valide existe ;
  • les dessins temporaires du bloc-note sont conservés dans sessionStorage par toolbox, outil et page, pas dans IndexedDB ;
  • si toutes les pages sont vides et que seule la page par défaut reste présente, l'entrée de module peut être supprimée.

Outil Mémos

Type : memos

{
  "tickerEnabled": true,
  "items": [
    {
      "id": "memo1",
      "text": "Penser à sauvegarder avant le boss",
      "color": "#f6c453",
      "updatedAt": "2026-08-21T12:00:00.000Z"
    }
  ]
}

Stockage compact :

  • tickerEnabled vaut true par défaut et est omis dans ce cas ;
  • updatedAt est omis si vide ou invalide ;
  • les mémos sans texte sont ignorés ;
  • si aucun mémo n'est valide et que la bande est active par défaut, l'entrée de module peut être supprimée.

Outil Checklist

Type : checklist

Format simple, sans catégorie :

{
  "items": [
    {
      "id": "item1",
      "label": "Potion",
      "qtyTarget": 10,
      "qtyCurrent": 3
    }
  ]
}

Format avec catégories :

{
  "hideCompletedSections": true,
  "hideCompletedSectionsFully": false,
  "sections": [
    {
      "id": "section1",
      "title": "Rey Dau alpha",
      "hideWhenComplete": false,
      "collapsed": false,
      "items": [
        {
          "id": "item1",
          "label": "Casque",
          "qtyTarget": 1,
          "qtyCurrent": 0
        }
      ]
    }
  ]
}

Stockage compact :

  • qtyTarget est omis si la valeur vaut 1.
  • qtyCurrent est omis si la valeur vaut 0.
  • hideCompletedSections est omis si false.
  • hideCompletedSectionsFully est omis si false.
  • title est omis si vide.
  • hideWhenComplete est omis si non défini.
  • collapsed est omis si false.
  • les sections sans item sont ignorées.

Outil Planificateur D'équipements

Type : equipmentPlanner

{
  "types": [
    {
      "id": "type1",
      "title": "Armes",
      "icon": "sword",
      "collapsed": false
    }
  ],
  "equipments": [
    {
      "id": "equipment1",
      "typeId": "type1",
      "name": "Épée runique",
      "icon": "sword",
      "active": true,
      "obtain": "Forge après le boss",
      "characteristics": [
        {
          "id": "trait1",
          "category": "Stats",
          "icon": "sword",
          "name": "Force",
          "value": 12
        }
      ],
      "socketItems": [
        {
          "id": "socket1",
          "name": "Joyau attaque",
          "shape": "jewel",
          "color": "yellow",
          "bonuses": [
            {
              "id": "bonus1",
              "category": "Compétences",
              "name": "Attaque",
              "value": 1
            }
          ]
        }
      ],
      "socketLinks": [
        {
          "id": "link1",
          "fromSocketItemId": "socket1",
          "toSocketItemId": "socket2"
        }
      ],
      "materials": [
        {
          "id": "material1",
          "name": "Minerai rare",
          "qty": 4
        }
      ],
      "categoryOrder": ["Stats", "Compétences"],
      "collapsedCategories": []
    }
  ],
  "typeOrder": ["type1"]
}

Outil Arbre D'objectifs

Type : goalTree

{
  "lastResetAt": 1786226400000,
  "orientation": "horizontal",
  "nodes": [
    {
      "id": "goal1",
      "parentId": "",
      "title": "Breuvage de rage",
      "kind": "step",
      "icon": "cube",
      "color": "gold",
      "qtyTarget": 10,
      "qtyCurrent": 0,
      "formula": "",
      "progressMode": "auto",
      "resetType": "none",
      "resetTime": "00:00",
      "weeklyResetDay": 1,
      "checkedAt": 0,
      "collapsed": false
    },
    {
      "id": "goal2",
      "parentId": "goal1",
      "parentIds": ["goal1"],
      "title": "Élixir de destruction",
      "kind": "step",
      "icon": "flask",
      "color": "cyan",
      "qtyTarget": 1,
      "qtyCurrent": 0,
      "formula": "base * 3",
      "resetType": "weekly",
      "resetTime": "05:00",
      "weeklyResetDay": 1,
      "checkedAt": 0,
      "collapsed": false
    },
    {
      "id": "goal3",
      "parentId": "goal1",
      "title": "Liste de farm",
      "kind": "checklist",
      "items": [
        {
          "id": "item1",
          "label": "Élément",
          "formula": "1",
          "qtyTarget": 1,
          "qtyCurrent": 1
        },
        {
          "id": "item2",
          "label": "Faire cela",
          "formula": "base",
          "qtyTarget": 5,
          "qtyCurrent": 2
        }
      ],
      "resetType": "none",
      "resetTime": "00:00",
      "weeklyResetDay": 1,
      "checkedAt": 0,
      "collapsed": false
    }
  ]
}

Stockage compact :

  • lastResetAt est omis si vide.
  • orientation est omis si la valeur vaut horizontal.
  • orientation vaut horizontal ou vertical.
  • parentId est omis pour les racines.
  • parentId reste le parent principal de placement dans le schéma.
  • parentIds contient les parents/prérequis pris en compte pour les liaisons de convergence ; il est omis si aucun prérequis supplémentaire n'existe.
  • les calculs base, le placement, la suppression de branche et l'agrégation de progression restent basés sur parentId.
  • un objectif peut donc être placé sous un parent principal tout en ayant d'autres prérequis, afin de représenter une convergence de branches.
  • kind est omis si la valeur vaut step.
  • kind vaut step, text ou checklist.
  • icon est omis si la valeur vaut cube.
  • icon vaut cube, fire, flask, grass, leaf, meat, powder, tear ou ball.
  • color est omis si la valeur vaut gold.
  • color vaut gold, cyan, violet, pink, green, gray, red, orange, yellow, blue ou indigo.
  • qtyTarget est omis si la valeur vaut 1.
  • qtyCurrent est omis si la valeur vaut 0.
  • formula est omis si vide.
  • progressMode est utilisé uniquement par les objectifs step; il vaut auto ou manual.
  • progressMode est omis tant qu'aucun choix explicite n'a été fait; par défaut, une étape avec enfants calcule sa progression automatiquement, sauf si tous ses enfants directs ont une cible de 1, auquel cas elle se comporte en mise à jour manuelle.
  • en auto, une étape avec enfants calcule sa progression depuis ses enfants; en manual, elle utilise sa propre qtyCurrent.
  • items est omis si vide ou si kind vaut step.
  • items[].label contient la ligne affichee dans un noeud text ou checklist; il peut etre vide pendant l'edition.
  • items[].formula est utilise uniquement par checklist pour calculer le total de la ligne ; la variable base reference la cible calculee du parent direct.
  • items[].qtyTarget est utilise uniquement par checklist et omis si la valeur vaut 1.
  • items[].qtyCurrent est utilise uniquement par checklist et omis si la valeur vaut 0.
  • resetType est omis si la valeur vaut none.
  • resetType vaut none, daily ou weekly.
  • resetTime est omis si la valeur vaut 00:00.
  • weeklyResetDay est omis si la valeur vaut 1.
  • checkedAt est omis si vide.
  • collapsed est omis si false.
  • Les parents invalides ou cycliques sont ramenés à la racine pendant la normalisation.

Stockage compact :

  • les types sans équipement sont ignorés ;
  • icon des types vaut boot, chest-armor, helmet, glove, shield, sword, gun, earring, necklace ou ring ;
  • icon des équipements utilise la même liste et pilote l'icône affichée sur la ligne de l'équipement ;
  • active est omis quand la valeur vaut true ;
  • obtain, characteristics, socketItems, socketLinks, materials, categoryOrder et collapsedCategories sont omis si vides ;
  • collapsed est omis si false ;
  • qty des matériaux est normalisé à minimum 1 ;
  • les caractéristiques et bonus sans nom sont ignorés ;
  • icon des caractéristiques directes vaut sword ou shield ;
  • shape vaut ball ou jewel, avec jewel par défaut ;
  • color vaut red, orange, amber, yellow, lime, green, teal, cyan, blue, indigo, violet, purple, pink, rose, white, gray ou black ;
  • les liens de sertissage invalides, dupliqués ou hors équipement sont ignorés ;
  • seuls les équipements actifs contribuent au résumé.

Outil Images

Type : images

{
  "images": [
    {
      "id": "image1",
      "label": "Map zone nord",
      "dataUrl": "data:image/webp;base64,..."
    }
  ]
}

Stockage compact :

  • label est facultatif.
  • label est omis si vide.
  • createdAt n'est pas stocké.
  • Le nom de fichier original n'est pas stocké.

Outil Annotation d'images

Type : imageAnnotation

{
  "image": "data:image/webp;base64,...",
  "markers": [
    {
      "id": "marker1",
      "x": 42.5,
      "y": 68,
      "label": "Entrée"
    }
  ],
  "drawings": {
    "strokes": [
      {
        "id": "stroke1",
        "color": "#f6c453",
        "width": 4,
        "points": [
          { "x": 18.5, "y": 32 },
          { "x": 24, "y": 40.5 }
        ]
      }
    ]
  }
}

Stockage compact :

  • un seul visuel par outil.
  • x et y sont des pourcentages entre 0 et 100.
  • label est facultatif.
  • label est omis si vide.
  • drawings est facultatif et omis si aucun trait valide n'existe.
  • les points des dessins sont stockés en pourcentages entre 0 et 100 pour rester alignés en aperçu réduit et en plein écran.
  • les dessins sont éditables uniquement depuis la modale plein écran ; l'aperçu réduit les affiche en lecture seule.
  • le mode temporaire des dessins d'image utilise sessionStorage et ne modifie pas drawings dans IndexedDB tant que l'utilisateur ne repasse pas en stockage permanent.

Outil Timer

Type : timer

Structure initiale :

{
  "activeTab": "stopwatch",
  "scrollResults": true,
  "sortResults": true,
  "stopwatch": {
    "elapsedMs": 12000,
    "startedAt": 1760000000000,
    "laps": [
      {
        "id": "timer1",
        "label": "Phase 1",
        "elapsedMs": 9000
      }
    ]
  },
  "countdowns": [
    {
      "id": "timer2",
      "label": "Boss",
      "type": "daily_time",
      "alertMode": "visible",
      "autoRefresh": true,
      "time": "12:03:00",
      "targetAt": 1760000000000
    },
    {
      "id": "timer3",
      "label": "Event",
      "type": "time_pattern",
      "pattern": "X:45:00"
    },
    {
      "id": "timer4",
      "label": "Event",
      "type": "interval",
      "autoRefresh": true,
      "intervalMs": 1800000,
      "startMode": "time",
      "startTime": "06:00:00",
      "anchorAt": 1760000000000,
      "targetAt": 1760001800000
    }
  ]
}

Types de compte à rebours :

  • duration : durée ponctuelle, avec durationMs et targetAt.
  • daily_time : occurrence à une heure précise, avec time au format HH:MM:SS, targetAt et autoRefresh optionnel.
  • time_pattern : prochaine occurrence automatique correspondant à un pattern HH:MM:SS, où chaque segment peut valoir X.
  • interval : intervalle basé sur intervalMs, anchorAt, targetAt, startMode, startTime et autoRefresh optionnel. startMode vaut now ou time; startTime est présent uniquement avec time. autoRefresh n'est conservé que si l'intervalle dure au moins 5 minutes ; les anciens intervalles sans targetAt restent interprétés comme répétables.

Pour time_pattern, un segment X situé après un segment plus large fixé cible le premier instant de l'occurrence. Exemple : X:24:X cible hh:24:00 et se répète toutes les heures.

Stockage compact :

  • activeTab est omis si la valeur vaut stopwatch.
  • scrollResults est omis si false.
  • sortResults est omis si false.
  • stopwatch est omis tant que le chrono est à zéro et qu'aucune étape n'est enregistrée.
  • elapsedMs est omis si 0.
  • startedAt est omis si le chrono n'est pas lancé.
  • laps est omis tant qu'aucune étape n'est enregistrée.
  • countdowns est omis tant qu'aucun compte à rebours n'est configuré.
  • countdowns[].alertMode vaut off, visible ou site, et est omis si off.
  • countdowns[].autoRefresh est omis si false.
  • countdowns[].startMode et countdowns[].startTime sont omis pour les intervalles qui démarrent à la création.
  • L'activation d'une alerte est bloquée dans l'interface si la fréquence configurée est inférieure à 5 minutes pour les comptes à rebours récurrents (time_pattern et interval). Le blocage ne dépend pas du temps restant avant la prochaine échéance.

Outil Calendrier

Type : calendar

{
  "initialized": true,
  "viewMode": "month",
  "weekStartsOn": 1,
  "events": [
    {
      "id": "calendar1",
      "title": "Raid",
      "startDate": "2026-08-14",
      "endDate": "2026-08-16",
      "repeat": "monthly",
      "monthlyRepeatBy": "weekday",
      "excludedOccurrences": ["2026-09-11"],
      "alertMode": "site",
      "alertOffsetMinutes": [10, 5, 0],
      "color": "violet"
    }
  ]
}

Stockage compact :

  • viewMode vaut week ou month ; week est omis dans ce cas.
  • initialized indique que le choix du type de calendrier a été validé dans l'outil ; il est conservé pour garder un calendrier vide utilisable.
  • weekStartsOn vaut 1 par défaut et est omis dans ce cas.
  • events est omis tant qu'aucun événement valide n'est configuré.
  • les événements sans titre ou sans cible de jour/date valide sont supprimés ; startMinutes reste requis en hebdomadaire et vaut 0 par défaut en mensuel.
  • dayIndex vaut 0 pour lundi, 1 pour mardi, jusqu'à 6 pour dimanche.
  • en calendrier mensuel, startDate et endDate sont des dates locales YYYY-MM-DD ; endDate est omis si la plage dure un seul jour.
  • en calendrier mensuel, monthlyRepeatBy vaut monthDay ou weekday ; monthDay est la valeur par défaut et est omise dans ce cas.
  • en calendrier mensuel, repeat vaut none, weekly ou monthly ; monthly est la valeur par défaut et est omise dans ce cas.
  • repeat=weekly est réservé aux entrées mensuelles par weekday ; si une entrée par monthDay demande weekly, elle est normalisée en none.
  • une entrée mensuelle répétée par monthDay n'a pas d'occurrence dans un mois où le jour demandé n'existe pas.
  • une entrée mensuelle répétée par weekday et monthly se répète sur le même rang de jour de semaine que startDate ; par exemple le premier lundi du mois.
  • excludedOccurrences contient des dates locales YYYY-MM-DD correspondant aux occurrences supprimées individuellement.
  • startMinutes et endMinutes sont des minutes depuis 00:00 dans la journée ; en hebdomadaire, l'interface saisit les heures au pas de 5 minutes tout en gardant une grille visuelle de 30 minutes ; en mensuel, les champs horaires ne sont pas exposés et startMinutes est omis s'il vaut 0.
  • endMinutes est conservé s'il produit une fin postérieure au début ; en mensuel, il peut être inférieur à startMinutes si la fin est sur un jour ultérieur.
  • en hebdomadaire, repeat n'est pas stocké dans le format cible : il est déduit de repeatable (weekly par défaut, none si repeatable=false).
  • en hebdomadaire, repeatable vaut true par défaut et est omis dans ce cas ; si false, l'entrée est supprimée quand son échéance de la semaine courante est terminée.
  • alertMode vaut off, visible ou site, et est omis si off.
  • alertOffsetMinutes est une liste composée de 10, 5 ou 0 (0 = au début et pendant l'événement pour les reprises de session) ; il est omis si l'alerte est désactivée ou si la liste vaut [10].
  • color vaut violet, gold, cyan, pink, green ou gray, et est omis si violet.

Import/export texte :

  • le format texte commence par @viewMode: week ou @viewMode: month, puis une ligne event | ... par entrée ;
  • en hebdomadaire, les champs day, start et end décrivent le jour de semaine et les heures locales (HH:mm) ;
  • en mensuel, repeatBy=monthDay ou repeatBy=weekday utilise startDate/endDate et repeat=none|weekly|monthly, par exemple repeatBy=weekday | repeat=monthly | startDate=2026-08-03 | endDate=2026-08-05 ;
  • l'import texte remappe toujours les identifiants d'événements.

Outil Task Planner

Type : taskPlanner

{
  "weeklyResetDay": 1,
  "resetTime": "00:00",
  "lastResetAt": 1760000000000,
  "hideCompleted": true,
  "categoryOrder": ["Raid", "Farm"],
  "collapsedCategories": ["Farm"],
  "tasks": [
    {
      "id": "task1",
      "title": "Quête 1",
      "description": "Préparer la route.",
      "category": "Raid",
      "type": "daily",
      "dailyResetTime": "06:30",
      "checked": true,
      "checkedAt": 1760000000000
    },
    {
      "id": "task2",
      "title": "Quête 2",
      "type": "weekly",
      "weeklyResetDay": 3
    }
  ],
  "relations": [
    {
      "id": "relation1",
      "fromTaskId": "task1",
      "toTaskId": "task2"
    },
    {
      "id": "relation2",
      "fromTaskId": "task2",
      "toTaskId": "task1",
      "prerequisite": true
    }
  ]
}

Notes :

  • type vaut unique, daily ou weekly.
  • weeklyResetDay suit les jours JavaScript : 0 dimanche, 1 lundi, ..., 6 samedi.
  • resetTime est global à l'outil et vaut HH:MM.
  • hideCompleted masque les tâches effectuées à l'affichage, ainsi que les catégories dont toutes les tâches sont effectuées.
  • dailyResetTime sur une tâche quotidienne surcharge l'heure globale.
  • weeklyResetDay sur une tâche hebdo surcharge le jour global.
  • category sur une tâche racine est facultatif. Les tâches enfants héritent de la catégorie effective de leur parent et ne stockent pas de catégorie propre.
  • Les tâches catégorisées sont regroupées par catégorie au niveau racine.
  • categoryOrder stocke l'ordre d'affichage des catégories utilisées. Les catégories absentes des tâches sont retirées à la normalisation.
  • collapsedCategories stocke les noms des catégories repliées.
  • relations stocke les parents directionnels. fromTaskId désigne la tâche enfant, toTaskId son parent.
  • une tâche ne conserve qu'un seul parent.
  • prerequisite: true marque aussi ce parent comme pré requis.
  • Les pré requis sont non bloquants : ils affichent un avertissement si le parent requis n'est pas effectué.
  • Le drag & drop conserve l'ordre manuel, et peut aussi ajouter une tâche racine à une catégorie existante ou la sortir vers les tâches sans catégorie.

Stockage compact :

  • weeklyResetDay global est omis si la valeur vaut 1.
  • resetTime est omis si la valeur vaut 00:00.
  • lastResetAt est omis si la valeur vaut 0.
  • hideCompleted est omis si false.
  • description est omis si vide.
  • category est omis si vide ou si la tâche a un parent.
  • categoryOrder est omis si aucune catégorie n'est utilisée.
  • collapsedCategories est omis si aucune catégorie n'est repliée.
  • checked et checkedAt sont omis si la tâche n'est pas effectuée.
  • dailyResetTime unitaire est omis si non défini ou identique à l'heure globale.
  • weeklyResetDay unitaire est omis si non défini.
  • relations est omis si vide.
  • relations[].prerequisite est omis si false.

Outil Liens

Type : links

{
  "categoryOrder": ["Builds", "Guides"],
  "collapsedCategories": ["Guides"],
  "links": [
    {
      "id": "link1",
      "title": "Build rogue",
      "category": "Builds",
      "url": "https://example.com/build"
    }
  ]
}

Stockage compact :

  • title est facultatif.
  • title est omis si vide.
  • category est facultatif et sert à l'autocomplete de création.
  • category est omis si vide.
  • url est obligatoire.
  • categoryOrder stocke l'ordre d'affichage des catégories, y compris les catégories vides.
  • collapsedCategories stocke les noms des catégories repliées.

Outil Compteurs

Type : counters

{
  "counters": [
    {
      "id": "counter1",
      "label": "Victoire",
      "value": 12
    },
    {
      "id": "counter2",
      "label": "Défaite",
      "value": -2
    }
  ]
}

Notes :

  • value est numérique.
  • Les valeurs négatives sont acceptées.
  • Les compteurs sans libellé ne sont pas conservés.

Outil Randomizer

Type : randomizer

{
  "activeTab": "wheel",
  "dice": {
    "faces": 6,
    "rolls": 3,
    "results": [2, 5, 1],
    "history": [
      {
        "id": "random1",
        "at": "2026-08-30T12:00:00.000Z",
        "results": [2, 5, 1]
      }
    ]
  },
  "wheel": {
    "draws": 2,
    "entries": [
      {
        "id": "entry1",
        "label": "Route farm",
        "weight": 3
      }
    ],
    "results": ["Route farm"],
    "history": []
  },
  "teams": {
    "teamCount": 2,
    "playerSelectionMode": "all",
    "playerSelectionCount": 2,
    "players": [
      {
        "id": "player1",
        "name": "Alice",
        "score": 10,
        "active": true
      }
    ],
    "exclusions": [
      {
        "id": "exclusion1",
        "playerAId": "player1",
        "playerBIds": ["player2", "player3"],
        "mode": "source"
      }
    ],
    "teams": [],
    "history": []
  }
}

Stockage compact :

  • activeTab est omis quand l'onglet actif est dice.
  • dice, wheel et teams sont omis quand ils ne contiennent que leurs valeurs par défaut et aucun résultat.
  • l'historique de chaque onglet est limité aux 20 derniers tirages.
  • les entrées de roue sans libellé sont ignorées, et weight est borné de 1 à 999.
  • les joueurs sans nom sont ignorés, et score est borné de 0 à 9999.
  • active vaut true par défaut sur un joueur.
  • playerSelectionMode vaut all ou count; en mode count, playerSelectionCount indique combien de joueurs actifs sont choisis aléatoirement avant la répartition.
  • chaque exclusion décrit un nom principal playerAId, une ou plusieurs cibles playerBIds et un mode.
  • mode: "source" interdit uniquement playerAId avec chaque cible; les cibles peuvent rester ensemble.
  • mode: "group" interdit chaque nom de la combinaison avec tous les autres noms de cette combinaison.
  • les exclusions invalides, dupliquées ou qui référencent un joueur absent sont ignorées.

Outil Simulateur de chance

Type : rngSimulator

{
  "activeRateId": "rate1",
  "rates": [
    {
      "id": "rate1",
      "label": "Drop rare",
      "rate": "2.500",
      "costPerDraw": "150",
      "multiplier": 50,
      "occurrenceValue": 30,
      "occurrenceUnit": "perHour",
      "realDraws": 120
    },
    {
      "id": "rate2",
      "label": "Ultra rare",
      "rate": "0.000004"
    }
  ],
  "settings": {
    "simulationDraws": 1000
  }
}

Stockage compact :

  • rates contient les taux enregistrés du module; un taux sans libellé est ignoré.
  • rate est une chaîne en pourcentage afin de conserver la précision saisie, par exemple "2.500" ou "0.000004".
  • un taux doit être strictement supérieur à 0 et inférieur ou égal à 100; les valeurs supérieures sont bornées à "100".
  • costPerDraw est une chaîne décimale optionnelle, associée au taux, utilisée comme unité libre pour les calculs de coût.
  • activeRateId référence un taux existant; sinon le premier taux est sélectionné.
  • rates[].multiplier est un bonus ou malus en pourcentage associé au taux, vaut 0 par défaut et le taux effectif est borné entre 0% et 100%.
  • rates[].occurrenceValue est numérique, positif ou nul, associé au taux, et vaut 0 par défaut.
  • rates[].occurrenceUnit vaut perHour ou perDay, et vaut perHour par défaut.
  • les anciennes données occurrenceUnit: "perMonth" sont converties en perDay, avec occurrenceValue divisé par 30.
  • rates[].realDraws est le total persistant de tirages réellement effectués pour ce taux, borné de 0 à 1 000 000 000.
  • settings.simulationDraws est borné de 1 à 1 000 000 et vaut 1000 par défaut.
  • settings est omis si simulationDraws vaut sa valeur par défaut.
  • rates[].costPerDraw est omis si vide ou nul.
  • rates[].multiplier est omis si sa valeur vaut 0.
  • rates[].occurrenceValue et rates[].occurrenceUnit sont omis si occurrenceValue vaut 0.
  • rates[].realDraws est omis si sa valeur vaut 0.
  • l'historique des simulations n'est pas persisté en IndexedDB; il est limité aux 20 dernières entrées en sessionStorage.

Import/export texte :

@activeRateId: rate1
[rates]
- Drop rare | rate=2.500 | active=true | costPerDraw=150 | multiplier=50 | occurrenceValue=30 | occurrenceUnit=perHour | realDraws=120

[settings]
simulationDraws: 1000

Outil Combos

Type : combos

{
  "device": "playstation",
  "categoryOrder": ["Neutral"],
  "collapsedCategories": ["Advanced"],
  "combos": [
    {
      "id": "combo0",
      "name": "Anti air",
      "device": "playstation",
      "inputs": [
        [
          { "kind": "direction", "value": "down", "holdMs": 2000 },
          { "kind": "button", "value": "triangle", "hold": true }
        ]
      ]
    },
    {
      "id": "combo1",
      "name": "Hadoken",
      "category": "Neutral",
      "device": "playstation",
      "inputs": [
        [{ "kind": "direction", "value": "down" }],
        [{ "kind": "direction", "value": "down-forward" }],
        [{ "kind": "button", "value": "cross" }]
      ]
    }
  ]
}

Notes :

  • device vaut playstation, xbox, switch, n64 ou keyboardMouse.
  • combos[].device peut figer le périphérique de rendu d'un combo, afin qu'il conserve ses couleurs si l'outil change de périphérique.
  • combos[].category est facultatif ; les catégories sont déduites automatiquement de cette valeur.
  • inputs est une liste d'étapes successives.
  • chaque étape contient une ou plusieurs entrées simultanées.
  • kind vaut button, direction, key ou mouse.
  • hold est facultatif et indique qu'une entrée doit être maintenue sans durée précise.
  • holdMs est facultatif et indique qu'une entrée doit être maintenue pendant cette durée en millisecondes ; l'interface limite la saisie à des secondes entières de 1 à 99.
  • categoryOrder stocke l'ordre d'affichage des catégories utilisées.
  • collapsedCategories stocke les noms des catégories repliées.
  • les catégories restent locales à l'outil ; l'utilisateur peut ajouter plusieurs outils Combos pour séparer des usages.

Stockage compact :

  • device est omis si la valeur vaut playstation.
  • category est omise si vide.
  • hold est omis si absent ou faux.
  • holdMs est omis si absent ou nul.
  • categoryOrder est omis si aucune catégorie n'est utilisée.
  • collapsedCategories est omis si aucune catégorie n'est repliée.
  • combos est omis si aucun combo valide n'existe.
  • les combos sans nom et sans touche sont supprimés.
  • les catégories et noms de combo sont limités à 80 caractères.
  • chaque combo conserve au maximum 64 étapes.

Outil Calculateur

Type : calculator

{
  "scrollResults": true,
  "entries": [
    {
      "id": "calc1",
      "label": "Lingots de fer",
      "value": 100,
      "formula": "100"
    },
    {
      "id": "calc2",
      "parentId": "calc1",
      "label": "Minerais de fer",
      "value": 500,
      "formula": "base*5"
    }
  ]
}

Stockage compact :

  • scrollResults est omis si false.
  • parentId est omis si vide.
  • label peut être vide.
  • value est numérique.
  • formula est omis si vide ; dans un calcul enfant, le mot base référence la valeur recalculée du parent.
  • Si un parentId ne pointe vers aucune entrée existante, il est réinitialisé à vide.

Outil Tableau

Type : table

{
  "rows": 12,
  "columns": 8,
  "cells": {
    "A1": "Texte libre",
    "B1": "100",
    "C1": "=A1+B1",
    "D1": "=(B1+20)/2"
  },
  "rowLabels": {
    "0": "Boss",
    "1": "Phase 2"
  },
  "columnLabels": {
    "0": "Item",
    "1": "Quantité"
  }
}

Stockage compact :

  • rows est omis si la valeur vaut 10.
  • columns est omis si la valeur vaut 6.
  • cells conserve uniquement les cellules non vides et dans les limites du tableau.
  • les espaces saisis dans une cellule non vide sont conservés.
  • rows est limité entre 1 et 50.
  • columns est limité entre 1 et 20.
  • Les valeurs de cellules sont stockées sous forme de texte brut, formules incluses.
  • Les résultats calculés ne sont pas persistés.
  • rowLabels et columnLabels conservent uniquement les intitulés visuels personnalisés.
  • Les intitulés personnalisés n'impactent pas les références de formule, qui restent basées sur les adresses A1, C2, etc.

Export

L'export d'une toolbox remappe les identifiants pour produire des IDs courts et indépendants.

Exemple :

{
  "toolbox": {
    "id": "t1",
    "name": "MHW",
    "modules": [
      {
        "id": "m1",
        "type": "checklist",
        "title": "Armures"
      }
    ],
    "updatedAt": "2026-07-25T12:00:00.000Z"
  },
  "modules": {
    "m1": {
      "items": [
        {
          "id": "i1",
          "label": "Casque"
        }
      ]
    }
  }
}

Import / Export texte des outils

Les outils compatibles proposent aussi un échange par copier-coller depuis le toggle import/export de leur header. Ce format est un format UI distinct du stockage IndexedDB et de l'export JSON global.

Règles communes :

  • l'import valide tout le texte avant de sauvegarder ;
  • une erreur de format ne modifie pas les données existantes ;
  • les IDs internes sont recréés à l'import ;
  • l'identité de contenu est évaluée après normalisation ;
  • les états de progression ne sont pas repris : checklist à qtyCurrent: 0, tâches non effectuées et checkedAt: 0.

Formats :

  • notepad : format structuré @notepad, blocs @page: Titre, @html / @endhtml pour le HTML riche nettoyé, meta @drawingMode: permanent et @drawings: {...} pour les dessins permanents. Un collage texte simple reste accepté et devient une page sans mise en forme.
  • memos : une ligne par mémo, avec préfixe - optionnel et suffixe couleur | color=#f6c453 ; les anciens préfixes * sont acceptés puis normalisés.
  • checklist : sections # Catégorie, lignes Item: quantité cible.
  • links : sections # Catégorie, lignes Titre: https://... ou URL seule; le suffixe optionnel | category=Catégorie reste accepté à l'import.
  • counters : lignes Libellé: valeur, valeurs négatives acceptées.
  • calculator : lignes Libellé: valeur | formula=base*5, indentation de deux espaces pour les enfants, meta @scrollResults: true. Le suffixe formula=... est optionnel et les anciens exports Libellé: valeur restent acceptés.
  • table : TSV pur accepté ; l'export peut ajouter @size, @columns, @rows puis @tsv pour préserver dimensions et intitulés.
  • combos : sections # Catégorie, lignes Nom | device=... | kind:value > kind:value+kind:value, avec suffixes [hold] ou [2s].
  • taskPlanner : sections # Catégorie, lignes indentées - daily Titre, - weekly Titre ou - unique Titre, metas @description, @dailyResetTime, @weeklyResetDay, @prerequisite.
  • equipmentPlanner : types # Type | icon=..., équipements ## Nom | icon=... | active=true, sous-sections Stats, Sockets, Craft.
  • images : blocs # Libellé puis data:image/...;base64,....
  • imageAnnotation : image base64, lignes @marker: x,y,label et @drawings: {...} pour les dessins permanents.
  • timer : metas globales, lignes lap | ... et countdown | ...; les échéances temporelles sont recalculées à l'import.

A maintenir à chaque update

Quand un outil change de structure :

  • mettre à jour ce document ;
  • mettre à jour les normalisations dans website/src/features/toolboxes/storage/toolboxStorage.js ;
  • mettre à jour les textes dans website/public/data/site.json si nécessaire ;
  • mettre à jour la validation dans tests/helpers/data-validation.mjs si nécessaire ;
  • vérifier import/export ;
  • lancer npm run check.