Logiciel à durée déterminée

Un itinéraire lumineux à travers Madagascar

Synchronisation WebSocket en temps réel, PostgreSQL en dessous, connexion sur mesure par-dessus. Trois voyageurs. Seize jours. Puis c’est fini.

Voici la cinquième et dernière expérience. En cinq articles, l’écart entre « décrire » et « ça marche » s’est rétréci. Pas parce que les outils se sont améliorés entre les articles, mais parce que moi, si. Un an à construire des dizaines d’apps de ce genre m’a appris quoi demander, comment structurer un prompt, quand insister, quand laisser l’outil avancer. TriboTrip, c’est le vibe coding quand on arrête d’expérimenter et qu’on se met à construire.

L’idée

Un voyage familial à Madagascar. Chez moi, ou ce qu’il en reste après trente ans d’ailleurs. Seize jours, trois voyageurs, six vols, quatre destinations. Dublin, Paris, Antananarivo, Sainte Marie, et retour. Ferries, vols intérieurs, hôtels, restaurants, activités. Le genre d’itinéraire qui vit habituellement entre trois fils d’e-mails, un Google Doc partagé que personne ne met à jour, et les messages WhatsApp de quelqu’un.

Le nom : Tribe + Trip. Un planificateur de voyage pour la tribu.

Je n’ai pas écrit « construis-moi un planificateur de voyage ». Des dizaines de projets m’avaient appris que chaque exigence laissée implicite est une décision que l’outil prend à notre place, généralement mal. J’ai donc spécifié : six types d’entrées (vols, hébergement, transport, activités, restaurants, visites). Synchronisation en temps réel pour que les membres de la famille voient les modifications instantanément. Authentification personnalisée pour que chaque membre de la famille ait son propre accès. Entrées multi-jours pour les hôtels. Gestion des fuseaux horaires entre l’Europe et Madagascar. Téléversement de fichiers pour les confirmations de réservation.

Cette précision, c’est ce qui change avec l’expérience. On apprend à parler la langue de l’outil. Ni du code, ni de l’anglais, quelque chose entre les deux : un ensemble de contraintes façonné par tout ce qu’on sait des endroits où le logiciel déraille. Chaque phrase qu’on ajoute réduit l’espace où l’outil peut prendre une mauvaise décision.

Regardez ce que ces phrases font concrètement. « Six types d’entrées », cela dit à l’outil quelles sortes de choses existent dans l’app avant qu’une seule ligne de code n’existe. Sans cela, on obtient un champ texte générique et on passe trois jours à remodeler l’app. « Synchronisation en temps réel », cela dit à l’outil qu’on ne veut pas d’une app où chacun passe son temps à rafraîchir la page ; ces quelques mots le poussent vers les WebSockets plutôt que vers du polling. « Gestion des fuseaux horaires entre l’Europe et Madagascar » et « entrées multi-jours » sont les cas tordus, énoncés d’emblée, et c’est précisément pour cela qu’on a évité les deux bugs les plus coriaces.

Et remarquez ce que le prompt ne dit pas. Aucune mention de React, Tailwind ou PostgreSQL. Pas de choix de base de données, pas de bibliothèque de composants, pas de structure imposée pour l’app. Spécifier la technologie, c’est sur-contraindre ; l’outil choisit une stack cohérente tout seul, et c’est très bien.

Spécifier le comportement, c’est là qu’on a du levier. Les premiers prompts de cette série spécifiaient des fonctionnalités : construis-moi un tableau périodique interactif, ajoute un mode quiz. Les prompts matures spécifient des frontières : voici où les données sont complexes, voici où les utilisateurs vont entrer en collision, voici où le calcul se complique.

Ce que j’ai obtenu

Quarante-trois commits sur trois jours. React, TypeScript, Tailwind, shadcn dans le navigateur. Express, PostgreSQL, Drizzle ORM, WebSocket côté serveur. Au bout du compte, cela donnait une vraie app avec une vue chronologique organisée par jour, des types d’entrées colorés, de la collaboration en temps réel, de la recherche de lieux, du téléversement de fichiers et une connexion sur mesure.

Vue chronologique montrant les vols du Jour 1 de Dublin à Antananarivo avec détails d'escale et check-in hôtel

Sous le capot

Six types d’entrées, chacun avec ses champs communs et ses particularités, plus des séjours sur plusieurs jours avec visualisations d’arrivée et de départ. Le schéma Drizzle seul fait une centaine de lignes. Mais la vraie histoire, c’est le système de mise à jour en direct. Toute la couche WebSocket tient en peu de code. Côté serveur, une seule fonction de diffusion :

function broadcast(wss: WebSocketServer, data: any) {
  const message = JSON.stringify(data);
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

// Chaque opération CRUD suit le même schéma :
app.post("/api/trip-entries", isAuthenticated, async (req, res) => {
  const data = insertTripEntrySchema.parse(req.body);
  const entry = await storage.createTripEntry(data);
  broadcast(wss, { type: "entry_created", data: entry });
  res.status(201).json(entry);
});

Côté client, un seul hook écoute ces diffusions et marque comme périmées les bonnes données mises en cache :

export function useWebSocket() {
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();

  const connect = useCallback(() => {
    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      switch (message.type) {
        case "entry_created":
        case "entry_updated":
        case "entry_deleted":
          queryClient.invalidateQueries({ queryKey: ["/api/trip-entries"] });
          break;
        case "member_created":
        case "member_updated":
          queryClient.invalidateQueries({ queryKey: ["/api/trip-members"] });
          break;
        // ... commentaires, photos, localisations
      }
    };

    ws.onclose = () => {
      reconnectTimeoutRef.current = setTimeout(connect, 3000);
    };
    wsRef.current = ws;
  }, []);

  useEffect(() => {
    connect();
    return () => {
      if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
      wsRef.current?.close();
    };
  }, [connect]);
}

Quand un membre de la famille ajoute un vol, le serveur diffuse l’événement. Chaque autre navigateur ouvert reçoit le message, comprend que sa copie locale n’est plus à jour, puis récupère la version fraîche au rendu suivant. Collaboration en temps réel. Sans rafraîchissement manuel. L’ensemble du système tient en environ soixante-dix lignes réparties sur deux fichiers.

Ce n’est pas un jouet. C’est une application complète, avec collaboration en temps réel, vraie base de données, vraie connexion utilisateur, et téléversement de fichiers.

Mais cette compacité a un prix. La couche de diffusion envoie chaque événement à tous les clients connectés, sous la forme de data: any : en gros, un paquet générique, sans canaux ni filtrage. Pour trois membres d’une famille, ça suffit. Pour trente, ce serait la première chose à refaire.

Ce qu’il a fallu corriger

Pas grand-chose.

Le système d’authentification a nécessité un pivot. L’authentification intégrée de Replit ne convenait pas au cas d’usage : je voulais que les membres de la famille se connectent avec un simple nom d’utilisateur et mot de passe, pas via un compte Replit. Quelques commits ont suffi pour passer à une auth personnalisée avec mots de passe hachés. Simple.

Écran de connexion montrant l'aperçu du voyage — 16 jours, 4 destinations, 6 vols, 3 voyageurs — avec authentification par nom d'utilisateur et mot de passe

Les calculs de durée de transport ont demandé plus de travail. Six ou sept commits pour affiner les cas limites autour des trajets multi-jours et du calcul de fuseaux horaires. Quand un vol décolle de Dublin à 10h00 GMT et atterrit à Antananarivo à 22h00 GMT+3, l’affichage doit tenir compte à la fois du décalage horaire et du changement de date. Le genre de chose facile à décrire et fastidieuse à implémenter correctement.

Deux problèmes. Une poignée de commits. Comparez ça aux neuf jours de débogage du NATO Alphabet Coach ou aux ajustements itératifs de la grille du Tableau Périodique. L’expérience ne rend pas les outils plus intelligents. Elle rend les prompts meilleurs, et de meilleurs prompts produisent moins de surprises.

L’écart

Il y a un écart entre ce qu’on imagine quand on dit « app générée par IA » et ce que les outils produisent réellement. Cet écart est plus large que ce que la plupart des gens supposent.

Paramètres montrant six types d'entrées colorés avec couleurs personnalisables en mode sombre

L’architecture n’a jamais été la partie difficile. L’outil sait produire la plomberie des mises à jour en direct et du stockage des données sans sourciller. La partie difficile, c’est de savoir quelle architecture demander. À un moment donné pendant la construction, j’ai cessé de penser à Replit, à la stratégie de prompting, à ce que l’IA pouvait ou ne pouvait pas faire. Je décidais si le ferry vers Sainte Marie nécessitait son propre type d’entrée ou relevait du transport. Si l’affichage du fuseau horaire devait montrer le décalage ou le nom de la ville. Des décisions produit, pas des décisions d’ingénierie.

Le vibe coding a fait baisser le coût de l’ingénierie. Les décisions, elles, sont restées coûteuses. Le code tourne. Je n’ai pas audité chaque dépendance. Pour seize jours de voyage en famille, ce n’est pas nécessaire.

Le voyage est devenu le sujet. C’est la vraie transition. Pas des mauvais outils vers de bons outils. De l’outil vers le problème.

La leçon

Une app d’entraînement à l’alphabet OTAN pour une seule personne. Un tableau périodique pour un seul étudiant en chimie. Un planificateur de voyage pour les vacances de deux semaines d’une seule famille. Sous l’ancienne économie, aucun de ces projets ne justifiait d’être construit. Pas de marché, pas d’équipe, pas de business case. Ils seraient restés des idées.

Ils existent parce que les construire coûte un week-end.

Ce basculement a été rapide, et l’essentiel de l’industrie ne s’en est pas aperçu.

Cette barrière s’est abaissée. Pas disparue : ces outils coûtent de l’argent, exigent une connexion fiable, et « un week-end de libre » est en soi une ressource que tout le monde n’a pas.

Pour un développeur à Lagos ou à Antananarivo, les mêmes outils servent moins à bricoler le week-end qu’à prendre de l’avance, à livrer pour des clients à une vitesse qui demandait autrefois une équipe. Pas un public d’une seule personne. Un revenu, une communauté qui a besoin de logiciels taillés pour des problèmes locaux.

Cette série décrit ce basculement vu comme un hobby, depuis une position assez confortable pour explorer. Mais les positions changent. J’ai grandi à Antananarivo. Je suis parti enfant, j’ai construit une carrière en France puis à Dublin, sans jamais perdre le lien avec chez moi. Le développeur resté là-bas ne construit pas pour un public d’une seule personne. Il construit pour les problèmes qu’il a sous les yeux, avec une compréhension de ces problèmes qu’aucun prompt depuis Dublin ne remplacera.

Ce qui commence comme personnel ne le reste pas toujours. L’espace entre « trop petit pour être construit » et « assez gros pour être vendu » était vide. C’est là que vivent les productypes, et il ne l’est plus.

Seul aux commandes

TriboTrip sera utilisé pendant seize jours par une famille. Puis ce sera terminé. Pas abandonné, pas déprécié. Achevé. Un logiciel qui a fait ce pour quoi il a été conçu, quand il le fallait, pour les gens qui en avaient besoin.

Mon métier, c’est construire du logiciel en équipe. Ces cinq expériences, c’était un seul développeur et une armée d’agents. De la même façon qu’un ingénieur gère aujourd’hui une flotte de milliers de machines, un développeur seul peut construire et maintenir une flotte d’apps. Je n’en vois pas encore les limites.

Ce qui reste

Il y a un an, j’avais peur que ces outils me rendent obsolète. Cette peur était trop simple. Ce qu’ils ont fait, en réalité, c’est me transformer en un autre type de bâtisseur, que j’apprends encore à connaître.

Ce n’est pas la première fois. Quand j’ai découvert ReSharper dans Visual Studio, coder a cessé d’être de la frappe. Renommer un symbole, sélectionner une destination, et la base de code se réorganisait dans une transe de raccourcis clavier. Martin Thompson appelle cela la sympathie mécanique : comprendre la machine assez bien pour se mouvoir avec elle plutôt que contre elle.

Ce qui prend forme aujourd’hui n’est pas tout à fait ça. La machine est différente ; posez-lui la même question deux fois et le code revient structuré autrement. On ne développe pas de sympathie pour quelque chose qui se réagence à chaque passe. Mais on peut en développer pour le problème. Trente ans à construire m’ont donné le sens de ce à quoi une solution devrait ressembler, comme un menuisier qui sait au son si un assemblage est serré. La sympathie s’est déplacée de la machine vers le matériau.

Mais voici ce que je ne sais pas encore. Le sens que j’ai du problème vient de trente ans d’essais et d’erreurs. Les outils accumulent les leurs. Chaque nouveau modèle devine mieux ce que j’ai laissé de côté dans le prompt. La distance entre « je sais quoi demander » et « l’outil le sait déjà » se réduit un peu plus à chaque fois. Si l’oreille du menuisier est le dernier avantage, que se passe-t-il quand la machine cesse de produire des assemblages mal serrés ?

Le vertige n’est pas parti. Il a juste cessé d’être tout le paysage.