mission Réservé All-in

Branche tes données

Rejoins-le pour progresser et valider tes étapes.

Se connecter pour rejoindre
Le contexte

BRIEFING MISSION

Jusqu'ici ton app a un squelette qui tient debout : un front qui s'affiche, un backend qui répond, une base qui existe. Mais elle est vide de sens. Cette mission lui donne de la chair, et dans l'ordre qui compte. D'abord on modélise les données, parce que c'est la colonne vertébrale de tout le reste. Une table bien pensée, c'est dix features faciles ; une table bricolée à l'arrache, c'est dix bugs à venir. Ensuite seulement, on livre une vraie feature de bout en bout : de l'action de l'utilisateur jusqu'à la persistance, jusqu'au réaffichage après un rechargement.

C'est exactement la logique de Captage : tu ne décides pas ce que tu gardes avant d'avoir une Vérité où le graver. La table d'abord, la feature ensuite. Tu apprends à penser en données, pas en écrans. Une fois cette mission validée, tu auras le geste fondamental du vibecoding en poche : transformer une intention en schéma propre, puis en chemin complet qui survit à un F5.

Consigne & checklist

OBJECTIFS MISSION

Consigne et checklist

Ta mission, que tu choisis d'accepter épicétou, est de poser ton modèle de table propre (la dédup gravée dans le schéma) et de livrer ta première feature cœur de bout en bout : action de l'utilisateur, donnée persistée, donnée réaffichée après rechargement.

La mission sera considérée comme réussie si…

  • Ta table existe avec une contrainte UNIQUE sur ta clé de dédup, écrite dans la migration (pas posée à la main dans un dashboard).
  • Ta première feature fait le tour complet : tu déclenches une action dans l'app, la donnée se grave en base, et elle réapparaît à l'écran après un vrai rechargement de page (F5).
  • Une double insertion volontaire de la même source ne crée aucun doublon : la requête de contrôle renvoie zéro ligne.
  • La logique d'écriture (dédup, ON CONFLICT, validation) vit côté serveur, jamais dans le composant d'affichage.
  • Ton JOURNAL porte ton inventaire de départ (la chose, ses infos, sa clé de dédup) et au moins un raté noté en chemin.

Pour aller plus loin

  • Ajoute la deuxième variante de l'écriture idempotente : ON CONFLICT DO UPDATE pour rafraîchir le titre quand la source existe déjà, au lieu de simplement l'ignorer. Tu auras alors les deux gestes en main : ignorer le doublon, ou réconcilier dessus.
  • Trie ou filtre ta liste côté base (par status, par created_at) plutôt qu'en mémoire dans l'écran. C'est un premier pas vers « la base décide, l'écran affiche ».
  • Va lire la table Vérité de Captage, où chaque item gardé se grave avec sa clé de dédup. C'est exactement le pattern que tu reproduis ici sur ta propre app.
Le pas-à-pas

ETAPES

La mission est découpée en étapes progressives :

  1. 1
    Étape 1 — Pense tes données avant d'écrire une ligne. Avant le moindre prompt, prends deux minutes pour répondre à ces trois questions, par écrit, dans ton JOURNAL : quelle est la chose que ta feature manipule (un item capté, une tâche, une note, une dépense) ? Quelles infos porte cette chose (un titre, une date, un statut, une source) ? Et surtout, qu'est-ce qui rend deux entrées identiques ? C'est ta clé de dédup. Dans Captage, deux flux RSS qui renvoient le même article ne doivent jamais créer deux rows : la clé, c'est l'URL. Cet inventaire AVANT d'agir, c'est la discipline vibecoding : une intention claire produit un schéma propre du premier coup ; une demande floue produit une table que tu retoucheras dix fois.
  2. 2
    Étape 2 — Fais écrire le modèle à Claude Code. Donne-lui ton inventaire comme intention, en exigeant la contrainte d'unicité et en interdisant l'exécution immédiate :
CODE
Crée la table `items` pour stocker une chose captée. Colonnes :
- id (clé primaire UUID, généré automatiquement)
- title (texte, requis)
- source_url (texte) — c'est notre clé de dédup
- status (texte, défaut 'inbox')
- created_at (timestamp avec fuseau, défaut maintenant)

Ajoute une contrainte UNIQUE sur source_url pour qu'on ne puisse
jamais avoir deux fois la même source. Écris la migration SQL,
ne l'exécute pas encore, montre-la moi d'abord.
  1. 1
    Étape 3 — Relis la migration AVANT de l'appliquer. Tu dois y voir la contrainte d'unicité écrite noir sur blanc. Quelque chose comme :
SQL
CREATE TABLE items (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title       TEXT NOT NULL,
  source_url  TEXT,
  status      TEXT NOT NULL DEFAULT 'inbox',
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (source_url)
);

Puis applique-la. Un seul changement à la fois : on crée la table, on vérifie qu'elle existe, et seulement après on branche la feature. Pour confirmer que la table est bien née, demande à Claude Code de lister sa structure, ou regarde-la dans ton dashboard de base :

CODE
La migration est appliquée ? Montre-moi la structure réelle de la
table `items` telle qu'elle existe maintenant en base (colonnes,
types, contraintes), pour que je vérifie que le UNIQUE est bien là.
  1. 1
    Étape 4 — Bake la dédup dans l'écriture, pas après. La contrainte UNIQUE empêche le doublon, mais elle plante si tu réinsères la même source. La bonne réponse, c'est ON CONFLICT : tu dis à la base quoi faire quand le doublon arrive, au lieu de la laisser exploser. Demande à Claude Code que ta fonction d'insertion soit idempotente, et que la règle vive côté serveur :
CODE
Quand on insère un item dont la source_url existe déjà, ne crée pas
de doublon et ne plante pas : utilise ON CONFLICT (source_url)
DO NOTHING (ou DO UPDATE si on veut rafraîchir le titre).
Mets cette logique dans une fonction côté serveur, PAS dans le
composant d'affichage.

Voici les deux formes que peut prendre l'insertion idempotente :

SQL
-- On ignore le doublon : la première version gagne.
INSERT INTO items (title, source_url)
VALUES ('Mon item', 'https://exemple.com/a')
ON CONFLICT (source_url) DO NOTHING;

-- On rafraîchit le titre si la source existe déjà.
INSERT INTO items (title, source_url)
VALUES ('Titre mis à jour', 'https://exemple.com/a')
ON CONFLICT (source_url) DO UPDATE SET title = EXCLUDED.title;

Cette phrase compte, retiens-la : la logique d'écriture vit côté serveur, jamais dans l'UI. L'écran demande, le serveur décide. C'est la ligne rouge de Captage transposée au code : le front propose, la couche métier tranche.

  1. 1
    Étape 5 — Livre la feature de bout en bout. Maintenant, le chemin complet. Une seule feature, mais entière :
CODE
Construis la première feature : ajouter un item.
- Dans l'UI : un champ titre + un champ source_url + un bouton « Ajouter ».
- Au clic : appelle la fonction serveur d'insertion (avec la dédup ON CONFLICT).
- Après l'insertion : recharge la liste depuis la base et affiche tous les
  items, du plus récent au plus ancien.
Fais le tour complet : action → persistance → réaffichage.

L'épreuve de vérité : ajoute un item, puis recharge la page (un vrai F5, pas un simple re-render). S'il est toujours là, la donnée a vraiment persisté. S'il disparaît, elle n'a jamais touché la base : tu affichais un état mémoire qui s'est volatilisé au rechargement.

  1. 1
    Étape 6 — Teste la dédup pour de vrai. Ajoute deux fois exactement le même source_url. La deuxième fois ne doit créer aucun doublon. Vérifie le compte de rows :
SQL
-- Combien d'items au total ?
SELECT count(*) FROM items;

-- Y a-t-il une source présente plus d'une fois ?
SELECT source_url, count(*) AS occurrences
FROM items
GROUP BY source_url
HAVING count(*) > 1;

La deuxième requête doit renvoyer zéro ligne. Si elle en renvoie une, ta dédup est cosmétique, pas réelle : soit la contrainte n'existe pas, soit ton insertion la contourne. Tant que cette requête ne renvoie pas zéro, la mission n'est pas finie.

Réservé All-in

La suite est réservée aux membres du parcours

Rejoins 3 semaines pour créer une App en partant de 0 pour débloquer la suite, les templates et suivre ta progression.