/abir
← Writing
2 min read421 words

Retrieval as an interface, not a feature

Most portfolios list technologies. This one lets you query them — and the whole page answers. Notes on building a retrieval graph as the primary navigation of a site.

A skills section is usually a wall of logos. It tells you what someone has touched, but not where, or how deeply, or whether the claim survives contact with real work. The interesting question isn't "do you know Docker" — it's "show me the Docker."

So this site treats that question as its primary navigation.

The graph is the data, not the decoration

Every skill, project and role is a node. An edge exists when a project's normalized tech tokens intersect a skill's signature — including aliases, so drf, djangorestframework and Django REST Framework all resolve to one canonical node.

ts
const skillToProjects: Record<string, string[]> = {};
 
const projectIds = projects
  .filter((p, i) => {
    const byTech = Array.from(projectTokens[i].tokens).some((t) => sigs.has(t));
    const byProse = patterns ? patterns.some((re) => re.test(projectText[i].text)) : false;
    return byTech || byProse;
  })
  .map((p) => p.id);

The important property: these edges are derived, never hand-authored. Add a project with tech: ['Qdrant'] and the Qdrant node gains an edge on the next build. Nothing to keep in sync, so nothing to drift.

Hover previews, selection commits

The rule that took the longest to find, and the one that mattered most:

  • Hover previews a skill — the graph lights up, the readout lists linked work.
  • Selection commits it — the entire page re-composes around the query.

Mixing these was miserable. When hover re-composed the page, moving the cursor across a tech tag collapsed rows above the cursor, and the row under your mouse jumped somewhere else. The fix wasn't easing curves or timing. It was recognising that one is a question and the other is a decision.

What a committed query does

  1. Unmatched projects collapse to a single line instead of fading.
  2. The experience timeline folds ruled-out roles into vertical spines.
  3. A persistent strip appears under the nav, with the live count and a way out.
  4. The URL gains ?q=nextjs, so the state can be sent to somebody.

That last one converts a visual gimmick into something useful: a link that answers a specific question before the recipient has to ask it.

See everything built with Next.jsNext.js

A recruiter asking "has he shipped anything with Next.js" can be handed a URL that lands on the portfolio already filtered — timeline folded, projects narrowed, count visible. The retrieval system stops being a toy the moment its state is addressable.

Traced to