mon portfolio c'est 42 dépôts et ça ne dit rien
dev · Oct 4, 2026 · 8 min read
le profil github derrière ce site a 42 dépôts publics. c'est la chose la plus complète que j'aie jamais faite, et c'est aussi la moins utile.
toute l'autodescription sur ce compte tient en six caractères : `20x dev`.
c'est le billet que je mettrais en premier, parce que c'est la raison pour laquelle tout le reste existe.
## à quoi ressemblent vraiment 42 dépôts
j'ai mesuré le profil avec l'api github au lieu de deviner, et voici le détail :
| quoi | combien |
| --- | --- |
| dépôts publics | **42** |
| dont des forks | 15 |
| sans aucune description | 12 |
| étoiles au total, les 42 réunies | 14 |
| forks au total | 1 |
| premier commit | 2023-01-29 |
| dernier push | 2026-09-22 |
et les langages, qui est l'endroit où le profil cesse d'être à propos de moi :
| langage | dépôts |
| --- | --- |
| JavaScript | 11 |
| Java | 9 |
| Python | 6 |
| rien (pas de git linguist) | 5 |
| CSS, Vue | 2 chacun |
| Rust, OCaml, PowerShell, Brainfuck, TypeScript, Svelte, HTML | 1 chacun |
neuf dépôts Java. presque tous de minecraft : un client, un plugin, un module de primes, un villageois dans un seau, une copie miroir d'une release publique. c'est une part réelle et honnête de mon histoire — j'étais un gamin qui aimait construire des choses pour un jeu — mais ce n'est pas un portfolio de design, et ce n'est pas ce que je fais maintenant.
donc un visiteur arrive sur ce profil et conclut l'une des deux choses : soit cette personne a cessé d'intéresser vers 2023, soit cette personne n'a jamais eu d'opinion sur quoi que ce soit. **les deux lectures sont fausses, et la page ne leur donne aucun moyen de le savoir.** ce n'est pas un problème de mise en forme. c'est un argument qui n'a jamais été fait.
## pourquoi une liste n'est pas un portfolio
une liste répond à « qu'est-ce qui existe ». un portfolio doit répondre à « pourquoi ».
42 lignes de `nom · langage · étoiles · description` c'est une réponse *complète* à la première question et une réponse *nulle* à la seconde. l'exhaustivité ne remplace pas un argument — elle le cache même, parce que 42 lignes ressemblent à des preuves et qu'aucune n'est une raison.
l'échec précis ici, c'est qu'**une ligne ne peut pas contenir une décision**. chaque ligne a la même forme, donc chaque ligne dit la même chose, et la seule chose qui les distingue est un nombre qui mesure l'attention des autres. triez la page par étoiles et vous obtenez quatre projets et beaucoup de vide. triez-la par `pushed_at` et vous obtenez un journal des modifications.
## ce que j'affiche réellement : 9 sur 42
ce site en affiche **neuf**. cela fait 21% du profil, et la sélection tient en neuf lignes d'un script de build, pas en une requête :
```ts
const FEATURED: { name: string; tags?: string[] }[] = [
{ name: "portfolio-finder" },
{ name: "dektop-cleaner" },
{ name: "terraria_mods_extractor" },
{ name: "4webvideo" },
{ name: "sorting-visualizer", tags: ["JavaScript", "React", "Visualization"] },
{ name: "wplace-theme-changer" },
{ name: "darkwplace-extension" },
{ name: "task-manager" },
{ name: "Frenxys" },
];
```
ce tableau, c'est le portfolio. pas la réponse de l'api, pas la base de données — neuf noms dans l'ordre où je veux qu'on les voie, tout le reste étant récupéré puis délibérément non affiché.
**l'ordre est la première décision de design, et c'est celle contre laquelle je me suis le plus battu.** l'api me donne le profil trié par `updated`, ce qui est la bonne réponse à « qu'as-tu touché en dernier » et une réponse inutile à « sur quoi dois-je regarder ». la récupération garde mon ordre et jette le tri :
```ts
/** keeps the curated order and warns loudly if a featured repo disappears. */
function selectFeatured(repos: GhRepo[]) {
for (const entry of FEATURED) {
const repo = byName.get(entry.name.toLowerCase());
if (!repo) { missing.push(entry.name); continue; }
picked.push(repo);
}
```
un projet de 2025 se retrouve à côté d'un projet de 2026 parce que c'est cela l'argument, pas à cause d'un horodatage. la récence n'est pas un argument.
la deuxième décision est plus discrète : **les neuf en vedette ont 8 étoiles à eux tous**, et quatre d'entre eux n'en ont aucune. si je'avais fait la curation à l'étoile, la page serait vide. les étoiles sont une propriété de l'attention des autres, pas du travail.
## chaque projet, et la décision qu'il incarne
c'est la partie qu'une liste ne peut pas faire. chaque carte de la grille des projets est là parce qu'elle incarne une décision que j'ai prise sur la façon dont ce site se comporte :
* **portfolio-finder** (JavaScript, 3 étoiles) — un outil qui trouve les portfolios des autres. il est là parce que ce site fait la même chose au moment du build : récupérer une fois, générer un fichier, et ne laisser personne interroger github à l'exécution. le repli est une liste codée en dur dans `constants.ts`, et il existe pour la même raison que cet outil existe.
* **dektop-cleaner** (JavaScript/electron, 2 étoiles) — il est là pour illustrer le point sur `pushedAt` : le champ est transporté dans le modèle de données et ne sert jamais à trier. un outil de 2025 mérite la même carte qu'un de 2026.
* **terraria_mods_extractor** (PowerShell, 0 étoile) — un simple script shell, et il reçoit la même carte qu'une application react. `buildTags` met toujours le langage en premier, donc le badge affiche `POWERSHELL` dans la même case avec le même poids. la grille ne classe pas par prestige, parce qu'une grille qui classe par prestige est un classement.
* **4webvideo** (Python, 0 étoile) — il est là pour une décision que je n'ai *pas* prise : il n'y a pas de filtre par langage ni de recherche sur neuf éléments. les deux coûteraient plus cher en code et en bruit visuel qu'elles ne rapporteraient.
* **sorting-visualizer** (JavaScript/React) — le seul projet avec des étiquettes écrites à la main, parce que les entrées de `FEATURED` acceptent une surcharge `tags`. les métadonnées automatiques sont un défaut, pas une cage : quand la machine ne sait pas décrire le travail, l'humain écrit la ligne.
* **wplace-theme-changer** et **darkwplace-extension** (JavaScript, 1 étoile chacun) — les deux traitent du même problème que j'ai ensuite résolu pour moi : repeindre à l'exécution une chose que vous ne possédez pas, sans recharger. le thème de l'extension, le thème de l'application. ils sont voisins du système de thème de ce site pour une raison, et cette raison est la seule raison pour laquelle ils sont sur la page.
* **task-manager** (JavaScript) — sa description github est *littéralement juste une url* : `https://taskmanager-enea.web.app/`. c'est pourquoi le type `Project` a un champ `homepage` après tout. la description devrait pointer vers la chose qui tourne, et quand elle ne le fait pas, le modèle a un autre endroit où la mettre.
* **Frenxys** (Brainfuck, 1 étoile) — un dépôt blague : un readme, et le même readme écrit en brainfuck. il est en vedette parce que la curation est éditoriale et qu'un portfolio n'a pas besoin d'être uniformément sérieux. si je retirais tout ce qui n'est pas sérieux, je retirerais aussi tout ce qui rendait le compte digne d'être regardé.
## la partie que j'ai supprimée, qui est la chose la plus honnête ici
la liste curatée contenait auparavant des entrées écrites à la main pour quatre projets plus anciens. je les ai retirées, et le commentaire dans `constants.ts` dit pourquoi :
> les entrées écrites à la main précédemment (demarkify, torr, beetrap, sniffcli et amis) ont été supprimées : ces dépôts n'existent que sous l'ancien compte propriétaire et renvoient une 404 sous Frenxys, donc les garder livrerait des liens morts.
j'ai vérifié, et c'est vrai. les quatre renvoient **404** sous `Frenxys` et **200** sous l'ancien compte. donc mon portfolio curaté livrait des liens vers rien.
c'est pire que de n'avoir pas de portfolio, parce que ça *paraît* curaté. un lien mort dans une liste choisie à la main, c'est un mensonge avec un design autour : il dit « j'ai choisi ceci » en ne pointant sur rien. **je préférerais neuf cartes vivantes que quinze mortes**, et j'en ai supprimé quatre pour que le chiffre soit vrai.
## ce que ce site a réellement coûté
parce qu'un billet comme celui-ci peut virer à l'autocongratulation, la facture. ce site est une application react rendue côté client avec :
* **189 urls** dans le sitemap — 35 routes, dont 7 de chrome et 28 de billets, chaque url n'existant que dans les locales qui ont réellement ce contenu — et un générateur qui **fait échouer le build** si un billet présent dans le code n'a pas d'url dans le sitemap, ce qui est le seul moyen que j'aie trouvé d'empêcher les pages orphelines
* une récupération github au moment du build avec repli, pour qu'aucun visiteur n'interroge jamais github
* un service worker, des assets hachés, un repeignage de thème à l'exécution, une version terminal `q`/ctrl+c servie à `curl`
* **28 billets, dont pendant des mois seuls 8 étaient réellement publiés** — un utilitaire `with_placeholder_copy` réécrivait les 20 autres avec du lorem ipsum à l'exécution, si bien que des brouillons terminés qui traînaient dans le code depuis un demi an étaient livrés comme du texte de remplacement
* une barrière `check:dist` qui **n'avait jamais échoué** : `ok()` affichait `FAIL` et oubliait ensuite de le compter, si bien que 1.710 assertions ne produisaient aucun effet
j'ai dépensé tout cela sur le cadre. **neuf projets sont visibles.** ce rapport est tout l'intérêt, et c'est l'argument en faveur du cadre : la liste n'a jamais été le problème, donc un effort passé sur la liste n'achète rien.
## pourquoi ce site existe
un profil github est une affirmation sans argument. ce site est l'argument.
il existe pour que chaque affirmation puisse être suivie du code qui la rend vraie et, là où je me suis trompé, du billet où je le dis. huit de ces billets sont réels et chacun se termine par une chose que j'ai ratée — un sniffer qui ne peut pas détecter ce que son readme annonce, un honeypot dont l'échec de bind est silencieux, un sitemap qui a laissé orphelines 37 pages, un thème livré avec des ombres codées en dur, un bug que j'ai corrigé avec trois éditions sans jamais le diagnostiquer, un repli qui a nécessité une réécriture pour un seul bot, et un audit de confidentialité qui a trouvé trois origines tierces dont j'ignorais l'existence.
**voilà le portfolio.** pas les 42 dépôts — la capacité à montrer le travail puis à refuser de cacher les coutures. une liste de liens ne peut pas faire ça. elle ne peut que pointer vers un dépôt et espérer que vous ne le lirez pas.
## ce que je dirais à mon moi d'avant
* **l'exhaustivité n'est pas un argument.** 42 n'est pas mieux que 9. si vous ne pouvez pas dire pourquoi une chose est dans la liste, vous n'avez pas fait de curation, vous avez fait un miroir — et les 15 forks prouvent que je sais à quoi ressemble un miroir.
* **supprimez avant de livrer des liens morts.** une liste curatée avec une entrée cassée est pire qu'aucune liste curatée, parce que la curation est l'affirmation et le lien est la preuve.
* **la liste n'a jamais été le problème.** je pensais sans cesse que la correction était d'écrire de meilleures descriptions. la correction était d'avoir quelque chose à dire.