my portfolio is 42 repositories and it says nothing
dev · Oct 4, 2026 · 8 min read
the github profile behind this site has 42 public repositories. it is the most complete thing i have ever made and it is also the least useful.
the entire self-description on that account is six characters: 20x dev.
this is the post i would put first, because it is the reason any of the rest of this exists.
what 42 repositories actually looks like
i measured the profile with the github api instead of guessing, and here is the whole thing:
| what | how many |
|---|---|
| public repositories | 42 |
| of those, forks | 15 |
| with no description at all | 12 |
| total stars, all 42 combined | 14 |
| total forks | 1 |
| first commit | 2023-01-29 |
| most recent push | 2026-09-22 |
and the languages, which is where the profile stops being about me:
| language | repos |
|---|---|
| JavaScript | 11 |
| Java | 9 |
| Python | 6 |
| nothing (no git linguist) | 5 |
| CSS, Vue | 2 each |
| Rust, OCaml, PowerShell, Brainfuck, TypeScript, Svelte, HTML | 1 each |
nine Java repositories. almost all of them minecraft: a client, a plugin, a bounties addon, a villager-in-a-bucket, a mirrored public release. that is a real and honest part of my history — i was a kid who liked building things for a game — but it is not a design portfolio, and it is not what i do now.
so a visitor lands on this profile and concludes one of two things: either this person stopped being interesting around 2023, or this person has never had an opinion about anything. both readings are wrong, and the page gives them no way to find out. that is not a formatting problem. it is an argument that was never made.
why a list is not a portfolio
a list answers "what exists". a portfolio has to answer "why".
42 rows of name · language · stars · description is a complete answer to the first question and a zero answer to the second. completeness is not a substitute for argument — if anything it hides the argument, because 42 rows feel like evidence and none of them is a reason.
the specific failure here is that a row cannot contain a decision. every row is the same shape, so every row says the same thing, and the only thing distinguishing them is a number that measures other people's attention. order the page by stars and you get four projects and a lot of empty space. order it by pushed_at and you get a changelog.
what i actually show: 9 of 42
this site shows nine. that is 21% of the profile, and the selection is nine lines of a build script, not a query:
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" },
];
that array is the portfolio. not the API response, not the database — nine names in the order i want them seen, with everything else fetched and then deliberately not shown.
the order is the first design decision and it is the one i fought hardest. the api hands me the profile sorted by updated, which is the correct answer to "what did you touch last" and a useless answer to "what should i look at". the fetch keeps my order and discards the sort:
/** 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);
}
a project from 2025 sits next to one from 2026 because that is the argument, not because of a timestamp. recency is not an argument.
the second decision is quieter: the featured nine have 8 stars between them, and four of them have zero. if i curated by stars the page would be empty. stars are a property of other people's attention, not of the work.
each project, and the decision it is standing in for
this is the part a list cannot do. every card on the projects grid is here because it stands for something i decided about how this site behaves:
- portfolio-finder (JavaScript, 3 stars) — a tool that finds other people's portfolios. it is here because this site does the same thing at build time: fetch once, generate a file, and let nobody query github at runtime. the fallback is a hardcoded list in
constants.ts, and it exists for the same reason that tool exists. - dektop-cleaner (JavaScript/electron, 2 stars) — it is here to make the point about
pushedAt: the field is carried into the data model and never used to sort. a 2025 tool earns the same card as a 2026 one. - terraria_mods_extractor (PowerShell, 0 stars) — a single shell script, and it gets the same card as a react app.
buildTagsalways puts the language first, so the badge readsPOWERSHELLin the same slot with the same weight. the grid does not rank by prestige, because a grid that ranks by prestige is a leaderboard. - 4webvideo (Python, 0 stars) — it is here for a decision i didn't make: there is no language filter and no search over nine items. both would cost more in code and in visual noise than they would return.
- sorting-visualizer (JavaScript/React) — the only project with hand-written tags, because
FEATUREDentries accept atagsoverride. the automated metadata is a default, not a cage: when the machine cannot describe the work, the human writes the line. - wplace-theme-changer and darkwplace-extension (JavaScript, 1 star each) — both are the same problem i later solved for myself: repaint a thing you do not own, at runtime, without reloading. the browser extension theme, the app theme. they are adjacent to the theming system of this site for a reason, and that reason is the only reason they are on the page.
- task-manager (JavaScript) — its github description is literally just a url:
https://taskmanager-enea.web.app/. that is why theProjecttype has ahomepagefield at all. the description should point at the running thing, and when it does not, the model has somewhere else to put it. - Frenxys (Brainfuck, 1 star) — a joke repo: a readme, and the same readme written in brainfuck. it is featured because curation is editorial and a portfolio does not have to be uniformly serious. if i removed everything unserious i would also remove everything that made the account worth looking at.
the part i deleted, which is the most honest thing here
the curated list used to have hand-written entries for four older projects. i removed them, and the comment in constants.ts says why:
the previous hand-written entries (demarkify, torr, beetrap, sniffcli and friends) were removed: those repositories only live under the old owner account and 404 under Frenxys, so keeping them would ship dead links.
i checked, and it is true. all four return 404 under Frenxys and 200 under the old account. so my curated portfolio was shipping links to nothing.
that is worse than having no portfolio, because it looks curated. a dead link in a hand-picked list is a lie with a design around it: it says "i chose this" while pointing at nothing. i would rather have nine live cards than fifteen dead ones, and i deleted four to make the number true.
what building this site actually cost
because a post like this can turn into self-congratulation, the invoice. this site is a client-rendered react app with:
- 189 urls in the sitemap — 35 routes, 7 of them chrome and 28 of them posts, where each url exists only in the locales that actually have that content — and a generator that fails the build if a post in the code has no url in the sitemap, which is the only way i found to stop orphan pages
- a build-time github fetch with a fallback, so no visitor ever queries github
- a service worker, hashed assets, runtime theme repainting, a
q/ctrl+c terminal version served tocurl - 28 posts, and for months only 8 of them were actually published — a
with_placeholder_copyhelper was rewriting the other 20 with lorem ipsum at runtime, so finished drafts that had been sitting in the code for half a year shipped as placeholder text - a
check:distgate that had never once failed:ok()printedFAILand then forgot to count it, so 1,710 assertions ran to no effect
i spent all of that on the frame. nine projects are visible. that ratio is the whole point, and it is the argument for the frame: the list was never the problem, so effort spent on the list buys nothing.
why this site exists
a github profile is a claim with no argument. this site is the argument.
it exists so that every claim can be followed by the code that makes it and, where i got it wrong, the post where i say so. eight of these posts are real and every single one ends with something i got wrong — a sniffer that cannot detect what its readme claims, a honeypot whose bind failure is silent, a sitemap that orphaned 37 pages, a theme that shipped with hardcoded shadows, a bug i fixed with three edits and never diagnosed, a fallback that needed a rewrite for one bot, and a privacy audit that found three third-party origins i had no idea were there.
that is the portfolio. not the 42 repositories — the ability to show the work and then refuse to hide the seams. a link list cannot do that. it can only point at a repository and hope you will not read it.
what i would tell my past self
- completeness is not an argument. 42 is not better than 9. if you cannot say why a thing is in the list, you have not curated, you have mirrored — and the 15 forks are proof that i know what mirroring looks like.
- delete before you ship dead links. a curated list with a broken entry is worse than no curated list, because the curation is the claim and the link is the evidence.
- the list was never the problem. i kept thinking the fix was to write better descriptions. the fix was to have something to say.