Il y a quelques années, j’ai commencé un projet pour un praticien en acupuncture qui souhaitait pouvoir suivre ses patient(e)s sur une application hébergée sur son serveur et garder le contrôle de ses données.

Sur l’application, différents cas d’usage existent. Un praticien peut :

  • enregistrer une nouvelle personne qui vient le consulter ainsi que sa séance
  • accéder à l’historique des séances pour une personne
  • saisir ses dépenses pour son cabinet
  • suivre ses revenus

On l’a ouvert à d’autres praticien·ne·s : c’est devenu un vrai produit utilisé au quotidien par un petit nombre de personnes ! J’y ai accumulé de la dette technique, et un de mes gros sujets de 2025 était de migrer le projet vers TypeScript.

Cette migration est finie et en prod depuis quelques mois ! Je prends donc le temps de faire le bilan calmement sur comment j’ai réussi à mener cette migration et sur l’évolution de mon usage de l’IA.

Contexte

L’organisation de mon projet a facilité sa maintenance : tests unitaires en place, architecture Atomic Design (surtout des molécules, des organisms et des pages, mes atoms étant les composants de Chakra).

J’ai choisi de migrer progressivement mon projet, ayant initié cette migration à un moment où c’était un peu le rush sur d’autres projets professionnels 😅

Une configuration dans le tsconfig.json permet à des fichiers JavaScript et TypeScript de cohabiter dans le même projet : allowJs: true. Ça m’a permis de déprioriser parfois cette migration au profit d’une correction de bug / demande de feature d’une personne utilisatrice.

Démarche

J’ai commencé par les plus petits composants - sans IA d’abord pour valider ma démarche. J’exploitais parfois les types natifs React pour les handlers DOM par exemple.

function handleChange(
  event: React.ChangeEvent<HTMLInputElement>,
  prop: keyof Expense,
) {
  let { value } = event.target;
  if (prop === "amount") {
    value = formatAmountMoney(value);
  }
  setExpense({ ...expense, [prop]: value });
}

Exemple sur cette fonction qui traite un formulaire permettant l’ajout d’une dépense financière.

Petite subtilité : le pattern { ...expense, [prop]: value } ne compile en mode strict que parce que toutes les propriétés de Expense sont des string — le montant est stocké sous forme de chaîne formatée, pas de number.

J’ai hésité à mettre en place react-hook-form sur ce projet mais je n’avais pas envie de me disperser sur trop de sujets.

Si tu as des retours d’expérience sur react-hook-form, n’hésite pas à m’en faire part dans les commentaires !

Une fois que j’ai réussi à migrer quelques petits composants, j’ai commencé à utiliser un agent Cursor pour itérer - tout en relisant ce qu’il faisait. J’avais besoin de d’abord mettre les mains dans le cambouis pour savoir quoi demander dans mon prompt ^^'

Utilisation d’agents

J’utilise Cursor comme IDE. Il a une fenêtre dédiée à la gestion d’agents. J’ouvrais un agent, voici le prompt que je lui donnais initialement - avec un exemple sur un composant qui affiche le détail d’une séance d’acupuncture.

Migre ce composant SessionLine.jsx vers .tsx. Je veux un typage strict des props (type dédié, pas de any), typage des retours de hooks, et des handlers d’événements avec les types React natifs (React.ChangeEvent<HTMLInputElement> etc.). Crée un test unitaire associé à ce composant couvrant les scénarios suivants :

  1. La sauvegarde d’une séance
  2. L’affichage de la dernière séance
  3. L’affichage d’une séance

Explique chaque choix de type non trivial.

J’ai remarqué que l’agent créait parfois des types en double : j’ai donc ajouté dans mes instructions de ne créer un type que si c’était nécessaire et qu’il ne trouvait pas un type exploitable dans le dossier types (où je place tous mes types pour l’instant).

Tous mes types sont isolés dans un même dossier. C’est là l’intérêt des fichiers comme AGENTS.md (ou Claude.md)- cette documentation liée aux agents spécifie de privilégier des types existants lorsque je les mets sur une tâche. On évite ainsi de le répéter à chaque prompt.

Je l’exécutais d’abord en mode “Plan” pour analyser ce qu’il allait faire puis le lançais en mode Agent et je faisais ensuite une review de son code.

L’interface de Cursor simplifie la relecture via l’IDE, on peut aussi y installer des skills et accéder à différents modèles (Composer, Grok, GPT5.6, Fable, Codex …).

Je n’ai pas exploité de skills particuliers dans le cadre de cette migration.

Les tests unitaires à l’ère des agents IA

Le projet était initialement très peu testé - en utilisant des agents, j’ai compris que j’allais devoir très vite en mettre en place.

Les tests sont encore plus primordiaux quand on commence à utiliser un agent IA pour coder : l’agent a besoin d’exécuter une commande pour vérifier la validité de son travail, c’est ce qui va lui permettre de détecter une potentielle erreur qu’il commet et la corriger en conséquence.

C’est une des choses qui m’enthousiasme le plus avec l’ère de l’IA : un argument de plus pour l’ajout de tests automatisés dans une codebase !

Filets de sécurité

Toujours pour donner un filet de sécurité à l’agent IA, j’ai mis en place des hooks de precommit afin de permettre l’exécution des tests unitaires et end-to-end (faits avec Cypress) avant chaque commit.

J’avais mis ces tests Cypress en place mais sans les exécuter dans un pipeline - la question de l’automatisation étant encore hasardeuse car la partie back du projet est sur un autre repo.

J’ai inclus le lancement des tests Cypress dans mon hook de precommit, mais ça risque de devenir très lent dans le temps. C’est quelque chose que je vais enlever une fois que j’aurai fusionné le back de mon projet avec mon front dans un monorepo.

"scripts": {
    "start": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "test": "vitest run",
    "typecheck": "tsc --noEmit",
    "cy:open": "cypress open",
    "cy:run": "cypress run",
    "format": "prettier --write ./src"
},
"pre-commit": [
    "format",
    "typecheck",
    "test",
    "cy:run"
],

Extrait de mon package.json - Si simple, mais si précieux pour les agents

À noter : c’est bien vitest run qu’il faut dans un hook (par défaut, vitest se lance en mode watch et ne rend jamais la main). Et attention au script format : prettier --write reformate les fichiers sur le disque mais ne les re-stage pas — sans un git add derrière (ou un outil comme lint-staged), le commit part avec la version non formatée.

Basculer sur un monorepo fait partie de ma longue todo pour ce projet 😅

Renforcement de la validité des données

TypeScript permet une validation à la compilation mais pas à l’exécution réelle. Or, quand on communique avec un système externe comme une API … Tout est imprévisible, le format des données peut très bien changer.

Je ne m’étais pas trop préoccupée du sujet, gérant moi-même l’API exploitée par le front, mais j’avais envie de me pencher sur le sujet pour améliorer la qualité de mon front.

J’ai installé zod, chose que j’ai longtemps procrastinée. L’utilisation de schémas Zod me permet de vérifier la validité des réponses d’un système externe lors du runtime. Et pas juste l’existence d’une propriété dans un JSON mais aussi sa bonne structure !

Plus j’utilise TypeScript, moins je m’imagine travailler dans un projet sans ! Il encourage vraiment à la rigueur dans son code, c’est vraiment appréciable.

Mise en place d’ADR

Ce projet a vraiment commencé en 2022 avec l’intention de monter en compétences sur React.

Aujourd’hui, il est utilisé au quotidien par plusieurs personnes : sa stabilité est nécessaire. Si je n’ai pas spécialement envie de monétiser ce projet, j’ai quand même envie qu’il reste utilisable et exploitable par ces praticien·ne·s en acupuncture sans que ça me prenne trop de temps.

Je me cherche encore sur la structure de mon ADR. Voici l’exemple de mon ADR sur le choix de l’architecture Atomic Design :

# ADR : Choix de l'architecture des composants et de la bibliothèque CSS

- **Date** : 2022-07-24

- **Contexte** : Le projet Acupio démarre sur un temps très restreint. On a besoin d'une bibliothèque de composants CSS nous permettant de faire un design minimaliste mais utilisable.

## Décision

Il a été décidé d'adopter une architecture "Atomic Design" (organismes, molécules) pour faciliter la maintenabilité du projet dans le futur.

(...)

## Conséquences

_(Section complétée a posteriori, en 2025)_

Cette décision a permis de faciliter la migration vers la bibliothèque Chakra v3 et a facilité la migration vers TypeScript. On a pu visualiser facilement les composants "feuilles" (les plus petits enfants) à migrer vers TypeScript avant de s'attaquer à des composants plus gros.

Il a été envisagé de se séparer de Chakra lors de la v3 qui a introduit un nombre de changements énormes. Cette migration a cependant été facilitée par l'utilisation d'agents IA pour la réécriture du code.

J’essaie d’y déposer vraiment tous mes souvenirs, mes hésitations mais je me questionne sur le format et le détail d’information à consigner dans ces ADR …

Bref, à voir comment ces documents vivent dans le temps ! Je pense que ça peut devenir une véritable mémoire du projet.

Conclusion

Même si cette migration est finalisée, j’ai quand même des petits ajustements post mise en prod - avec par exemple l’utilisation du type React natif PropsWithChildren, je suis complètement passée à côté et j’ai dû le recréer …

C’est déjà une étape très positive de ne plus avoir de code JavaScript sur mon projet.

TypeScript encourage à la rigueur et l’utilisation d’agents IA me force à redoubler de vigilance sur la présence des tests.