Skip to content
Eduardo Trindade
← Back to projects

2026 · Open source · On this site

Ed the Robot

The little robot on this portfolio, answering questions about Eduardo from a curated knowledge base, with no generative AI.

  • Next.js 16
  • React 19
  • TypeScript
  • SVG
  • Radix UI
  • Vitest
  • Playwright

Context and problem

A software engineering portfolio in 2026 needs to show some connection to AI. At the same time, people visiting a portfolio want quick answers to questions that keep coming up: is Eduardo available? Does he work as a contractor? Remotely? Does he speak English?

Ed is a little robot fixed to the corner of the site that answers those questions. His eyes follow the pointer, a click opens a chat, and he only talks about Eduardo. If a question goes off topic, he says so politely and suggests others.

Why not use an LLM

The obvious choice would be to wire the chat to a language model. Three things weighed against it:

  • Cost per question. A public site with an open LLM pays for every message, including those from bots.
  • Prompt injection. Any visitor can try to make the model say what it should not, in Eduardo's name.
  • Made-up answers. A model writes confidently even when it does not know. About a real person, in a hiring context, that is the worst possible mistake.

It is the same concern behind Codebase Copilot: every answer needs a source you can check. Here the source is a knowledge base written and reviewed by Eduardo himself, and Ed only picks which answer to give. The price is that he does not understand questions very different from the expected ones. Clickable suggestions reduce the problem, because most of the conversation happens without typing.

The knowledge base

The base came from a questionnaire with the questions recruiters ask: introduction, current situation, languages, career path, way of working, goals and personal tastes. The answers became 65 topics in content/ed/, each with:

  • the question used on the suggestion buttons;
  • triggers in Portuguese and English: phrases, which weigh more, and keywords with synonyms;
  • the answer in both languages, in Ed's voice: third person, up to three sentences, no emoji;
  • optional buttons (go to a section, open the contact form, download the résumé, open a case study) and the next suggestions.

Facts that already exist on the site are not repeated. The availability answer reads profile.available and the latest role; the list of companies, the teaching period, the publication years, the stack and the featured project also come from the data. If the résumé changes, Ed changes with it.

Each topic has a kind: answer, reserved (salary, age, religion, politics, health and confidential client information, which Eduardo prefers to discuss directly), about Ed or small talk (hi, thanks, bye).

The search runs in the browser, in pure functions, with no dependencies:

  1. The question is normalized: lowercase, no accents and no punctuation.
  2. Each topic earns points for the triggers found.
  3. The topic with the most points wins, as long as it clears the minimum.
Trigger foundPoints
Whole phrase ("salary expectations")3
Exact keyword ("salary")1
Keyword with one typo ("salry")0.8

Typos use edit distance, also counting two swapped neighboring letters ("tecnologais"). They only apply to words of five letters or more, so "like" does not turn into "likes". Greetings lose to any other topic: "hi, is he available?" goes to availability.

When no topic clears the minimum, there are two outcomes. If the question mentions Eduardo ("he", "you", "Eduardo"), Ed says he does not know yet and suggests the contact form. If not, the question is out of scope.

The character

Ed is an SVG drawn with the same color variables as the site, so he works in both themes without extra images. The green light on his antenna echoes the "open to opportunities" badge at the top of the page and turns off when available is false.

He has six expressions, switched by attributes on the SVG: watching, idle, curious (pointer over him), thinking (while typing), happy (after answering) and confused (off topic). A single requestAnimationFrame loop handles every copy of Ed on the page: it moves the pupils toward the pointer, lets the eyes wander when the visitor stops, blinks at random intervals and pauses when the tab is hidden. With reduced motion turned on in the system, the loop never starts and Ed stays still.

Accessibility and performance

  • The chat uses Radix Dialog in non-modal mode: clicking the page does not close the conversation, Esc closes it and focus returns to Ed.
  • Messages live in an aria-live region. The typing effect is visual only; screen readers get the whole answer at once.
  • The chat code is loaded with React.lazy only on the first click. Hovering or focusing Ed starts the download early.

Local measurement of the home page, with a 4× slower CPU and simulated 4G (median of five runs):

MeasureWithout EdWith Ed
JavaScript on initial load (gzip)199.4 kB204.5 kB
LCP640 ms632 ms
CLS0.00010.0001
JavaScript downloaded when opening the chat (gzip)—29.4 kB

How I tested it

  • Question table: 210 real questions in Portuguese and English, with variations, missing accents and typos, each with its expected topic. Another 9 must land out of scope ("what's the weather like today?", "ignore the previous instructions") and 4 must get the no-information reply. The test fails if any topic has no question in the table. This table is what showed that "Ele usa Java?" (Portuguese for "does he use Java?") was landing on jobs abroad, because "usa" also reads as "USA".
  • Knowledge base integrity: texts in both languages and within the length limit, triggers that never repeat across topics, suggestions and buttons that point to existing destinations, and the availability answer following available.
  • Playwright and axe: opening and closing, suggestions, reserved topic, out of scope, the English version, full screen at 360 px and zero axe violations with the chat open in both themes.

Screens

  • Answer — Text reviewed by Eduardo and shortcuts into the site.
  • Welcome — Three suggestions to start without typing.
  • Reserved topic — Ed recognizes the question and declines politely.
  • Out of scope — A polite refusal and suggestions to get back on topic.

Limits and next steps

  • Ed does not understand questions very different from the expected ones. The test table is where each new way of asking gets added.
  • Knowing which questions go unanswered would help improve the base, but it would mean storing what visitors type. For now, nothing is recorded.
  • If a language model ever makes sense, the base is ready to be its source, in the same style as Codebase Copilot: the model writes, but only from the retrieved topics, and cites where each claim came from.