Building Apps from Components — A Human Guide
The Loop
If you’ve ever built a working prototype, made one small change, and watched the whole thing break — and then couldn’t find what broke, and then started over from scratch — this is for you.
The pattern looks like this:
- Build a working prototype in pure HTML and JS.
- Add a feature or fix something.
- Something else breaks. You don’t know what.
- No automatic check tells you what’s wrong.
- You click around the app for a while, hunting.
- It would take longer to fix than to start over.
- So you start over.
- Repeat.
This isn’t a you problem. It’s a structure problem. When the whole app is one blob, every change can break any part of it, and there’s no fast way to find out what broke. The fix isn’t “be more careful.” The fix is to build the app differently — out of small, independent pieces, each with its own test.
The rest of this guide shows what those pieces are, why they make the loop disappear, and how Hermes builds apps this way.
A Different Way to Think About a Page
A web page isn’t one thing. It’s a collection of pieces sitting next to each other.
Look at almost any page:
- a header at the top
- a search box
- a button
- a list of items, each with a checkbox and some text
- a footer
Each of those is a piece. A component is one of those pieces. A page is a sandwich of components, not a single solid loaf.
What is a Component?
A component is a chunk of a web page with a single job.
It has:
- a look — how it appears
- behavior — what happens when you click, type, or hover
- a boundary — what’s inside it and what’s outside
The simplest example: a Button. One job — be a button. Looks like a button. Behaves like a button. Nothing else.
A slightly bigger one: a TodoItem. One job — show a single task. It shows the task’s text, a checkbox, and tells the rest of the page when the checkbox is clicked. Nothing else.
Components as Fillable Templates (Props)
You rarely want the button. You want this button with this label, this color, this size.
A component takes props — short for “properties” — which fill in its blanks. Think of props like filling out a form:
“Button: make me a red one, with text ‘Save’, and slightly bigger.”
That’s three props: color=red, text="Save", size=large. The Button component is the template; the props are the answers to its questions.
A real example from the TODO list: the TodoItem component takes the text of the task and a done flag as props. Same component, used dozens of times, each instance with different text and a different done flag. You don’t write a new component per item — you reuse the same one with different props.
Components That Remember (State)
Some pieces need to remember things between moments — what you typed, what’s checked, what’s in the list right now.
That short-term memory is called state. When a component’s state changes, the component redraws itself to show the new memory. Type a character in a search box → state changes → the box redraws with the new character. Check a checkbox → state changes → the item redraws as done.
A key distinction: state is the component’s short-term memory. It forgets when you reload the page. The database is the long-term memory — it remembers across reloads, across devices, across days. They are different things, and confusing them causes bugs.
In our TODO list:
- The list of items itself is state. When you add a new one, the list’s state changes and the page redraws to show it.
- Whether a particular item is done or not is a prop on
TodoItem— it gets passed in. (Where it lives long-term is a different concern; that’s the database’s job.)
You don’t need to know the mechanics of how state is tracked. Hermes handles that. You need to know that some components remember things, and that’s the piece that makes the page feel alive.
Why This Breaks the Loop
This is the heart of it. Compare two ways of building:
| Monolithic prototype | Component-based |
|---|---|
| One big HTML+JS blob. | Many small, named pieces. |
| Change anywhere → might break anywhere. | Change in a component → only that piece is at risk. |
| “Is it broken?” answered by clicking around. | “Is it broken?” answered by a test, in seconds. |
| Fixing means archaeology. | Fixing means the failing test points at the exact piece. |
| When it’s too broken, start over. | When one piece breaks, fix that piece, run its test, move on. |
The “blast radius” of any change goes from “the whole app” to “one component.” That’s the structural fix for the loop.
Styling That Goes Where It Belongs
The same locality applies to styling. Each component carries its own styles. They don’t leak to other components.
Hermes writes styles using Tailwind — a system of small class names that control spacing, color, size, borders, and so on. We don’t need to dive into the class names; you describe what you want, Hermes writes them.
This is why components and styling are exciting together: you can tweak the look of one piece — make a button more purple, give cards more breathing room — and the rest of the app stays put.
If you ever peek at a component’s code, the class attribute will look like alphabet soup:
class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg"
That’s normal. You don’t read it; you describe the look, Hermes writes the classes.
Hermes also keeps a small shared style config so colors and spacing stay consistent across components. You don’t need to think about it — but it means “more purple” gives the same purple on every page.
Tests as the Per-Component Safety Net
Each component has a small test: “given these props, the component should look like X.”
Tests run in seconds. When one fails, the failure message names the component and tells you exactly what was expected vs. what it got. Hermes runs them, reads the output, and tells you what passed and what failed. You don’t have to.
A test for TodoItem in plain English:
Given:
text="Buy milk",done=falseShould show: the text “Buy milk” and an unchecked box.Given:
text="Buy milk",done=trueShould show: the text “Buy milk” with a line through it, and a checked box.
A test for the page (TodoList):
Given: no items in the list Should show: an empty-state message (“Nothing yet — add your first todo”).
Given: two items in the list Should show: both items, plus the input.
If something does break, the failure is specific:
✗ TodoItem test failed
Expected: text "Buy milk" with line-through when done=true
Got: text "Buy milk" with no line-through
I probably broke this changing styles. Fix, revert, or show me?
The test name tells you the component. The message tells you what was wrong. You don’t hunt.
The TODO List, From Idea to Live
A full walkthrough. As we covered in the workflow guide, this all happens on a branch in your dev copy first. The live app stays untouched.
1. Plan
You: “Hermes, build me a TODO list page from components.”
Hermes: “I’ll plan the components first. Here’s the shape:
TodoList (the page)
├── TodoInput (text box + "Add" button)
└── TodoItem × N
├── checkbox
└── the task text
Three components. I’ll build them bottom-up, with tests, on branch feat/todo-list in your dev copy. OK to start?“
2. Build TodoItem (and style it)
Hermes: “Building TodoItem first. It takes text and done as props, renders a checkbox and the text, fires a callback when the checkbox is toggled. Writing its test now.”
TodoItem tests: ✓ done=false, ✓ done=true. Quick check: ✓.
You: “Hermes, when a todo is done, strike through the text and gray it out.”
Hermes: “Updating TodoItem styles… Done. Test still passes. Quick check: ✓.”
3. Build TodoInput
Hermes: “Building TodoInput next. A text box and an ‘Add’ button. The button is disabled when the box is empty. Writing its test.”
TodoInput tests: ✓ empty disables, ✓ text enables. Quick check: ✓.
4. Build TodoList
Hermes: “Now TodoList. It uses TodoInput and TodoItem. It holds the list of items in its state. When you click ‘Add’ in TodoInput, the list adds a new item. When you toggle a checkbox in TodoItem, the list marks that item done. Writing its tests.”
TodoList tests: ✓ empty, ✓ renders, ✓ add, ✓ toggle. Quick check: ✓.
5. Full Check
Hermes: “All three components built, tested, and assembled. Running full check to verify they all work together — empty state, adding, toggling, the styling.”
[~30 seconds later]
Full check: ✓ 12 component tests, ✓ add flow, ✓ toggle flow, ✓ styling.
Hermes: “Full check passed. Ready to push to GitHub for the safety gate?”
6. Safety Gate & Deploy
This is the same ritual as the workflow guide. Hermes pushes the branch to GitHub, the safety gate runs, you review the PR, click Merge, then say “Deploy it.” Done.
What You Say to Hermes
A small starter set. As in the workflow guide, you don’t run commands — you describe and approve.
| You want to… | You say… |
|---|---|
| Build a new page | “Hermes, build me a [page] from components.” |
| See the plan first | “Hermes, what components do I need for this?” |
| Add a missing test | “Hermes, add a test for the [component].” |
| Tweak a component | “Hermes, change [component] so [behavior].” |
| Style a piece | “Hermes, make this button more purple.” |
| Style a state | “Hermes, strike through TodoItem text when done.” |
| Re-space something | “Hermes, the cards feel cramped — give them more breathing room.” |
| Diagnose a failure | “Hermes, which test is failing and why?” |
| Revert a single piece | “Hermes, undo the change to [component].” |
| Start a new app | “Hermes, set up a new app using Vite, Vite-Express, React, and Tailwind.” |
| Check the stack | “Hermes, is this app using Vite, Vite-Express, React, and Tailwind?” |
| Add a missing tool | “Hermes, add Tailwind to this app.” |
When Things Break (the new normal)
The old normal: vague “something’s wrong,” long hunt, give up, start over.
The new normal: a test fails, the test name tells you which component, the message tells you what’s wrong, you (or Hermes) fix that one piece, the test turns green, you move on.
You don’t restart because no change is big enough to require it. Each component is small. Each change is small. The tests catch breaks at the size of a component, not at the size of the whole app.
What You Don’t Need to Know
You describe, you approve, you intervene when needed. You don’t:
- write React
- read JSX line-by-line
- run tests by hand — Hermes does
- choose which component owns which state — Hermes plans, you approve
- manage the build pipeline — it’s set up
Hermes handles the technical work. You focus on what the app should do and how it should feel.
The Stack
When Hermes builds an app, it uses four tools. You don’t operate any of them — but it’s useful to know they exist and roughly what they do, so you can ask good questions.
| Tool | What it is, in plain English |
|---|---|
| Vite | The dev server. Runs your app while you build it; refreshes the page when Hermes makes a change. |
| Vite-Express | The bridge. Lets the same app run in development (Vite) and on the live server (Express) without two codebases. |
| React | The component model. The “pieces” we covered throughout this doc are React components. |
| Tailwind | How Hermes writes styles. (Covered in the styling section above.) |
When you start a new app, make sure Hermes is using Vite, Vite-Express, React, and Tailwind for your app. If any of the four is missing, ask Hermes to add it before you start building components — switching mid-build is painful.
Summary
- Apps are collections of components.
- Props make a component a fillable template.
- State is the component’s short-term memory; the database is the long-term memory.
- Each component has its own test.
- Components + Tailwind: styling is fast, local, and low-risk.
- Changes stay local; tests catch breaks; you patch, not restart.
- The stack: Vite, Vite-Express, React, Tailwind. Confirm it’s in place before you start building.
- You describe and approve; Hermes builds, tests, and ships.