Appearance
From data to answers
It all comes down to this: you have data, you want answers. Everything else — ingestion, model, query, chart, deployment — is in service of that single trip. Anything that isn't reducing the distance between "I have a question" and "I have an answer that changes what I do next" is overhead.
This doc is about the route. The smallest possible architecture you can start with, and how it grows one question at a time. Each question becomes a Stride card, and each card moves the architecture exactly far enough to answer it. No further.
The frame
Build for the question on the card, not the question you might one day be asked.
Architecture earns its place by answering a question. If no card needs it, it doesn't get built. The shape of the system at any moment is a record of the questions you've actually faced — not a prediction of the ones you might.
The starting rung — one sheet, one module, one page
The simplest architecture that delivers an answer:
| Layer | What it is | Why it's enough |
|---|---|---|
| Data | A CSV, a Google Sheet, or a single API endpoint | Whatever you can read with one function call is your source |
| Access | A Python script using Pandas to load, shape, and write the answer to a JSON file | One file. Pandas does the heavy lifting; you write almost no glue. |
| View | A page in the app-starter (TypeScript, Next.js) that reads the JSON and renders it | Your answer, visible. |
That's three files. No database, no ORM, no migrations, no scheduled jobs. It runs locally, it answers the question, and it can be thrown away without grief if the question changes.
Two languages, deliberately. Pandas is the right tool for shaping data — slicing, joining, aggregating — and the Python ecosystem around it is unbeatable. The app-starter is the right tool for the page — TypeScript, Next.js, shadcn. Don't force one language to do both. The handoff is a JSON file the Python script writes and the page reads. That's the whole interface between them.
If your first question is "how much did we invoice each customer last quarter?" and the data is in a spreadsheet, this is the architecture. Building more is You Ain't Gonna Need It ("YAGNI").
If there's no spreadsheet — the source is a SaaS API only — the shape doesn't change. The Python script hits the API instead of reading a file, writes the same JSON. Same three layers, same simplicity.
The Stride loop — one card per question
Each question becomes a Linear issue. The card carries:
- The question — phrased in business terms, not technical ones
- The CRAFT prompt — what Claude needs to know to answer it
- The deliverable — usually a chart, a table, or a number on the page
The card moves: Backlog → Todo → In progress → In review → Done. Done means the answer is on the page and someone has looked at it.
| Card | Architecture move | What grew |
|---|---|---|
| "How much did we invoice each customer last quarter?" | Python loads the sheet and sums by customer; the page renders the table | The spreadsheet, the Python script, the page |
| "Which customers are buying less than last year?" | Add a year-over-year column to the Pandas output; the page picks it up | One function |
| "Same question but split by region" | Group by region in Pandas; add a column to the table | One column |
| "Compare invoicing against Stripe payments" | Two sources. This is the moment. | Time for a data model in the middle |
Each card is a stepping stone. None of them build infrastructure speculatively. The architecture is a record of the questions you've been asked, not a guess at the ones you haven't.
When to climb a rung
The architecture grows when the current shape stops being the simplest answer. The signals are predictable:
| Signal | What it means | What to do |
|---|---|---|
| Two sources, one question | You need a join across systems | Climb to a data model in the middle |
| The same query written three times | A helper is forming | Extract a function in the access module |
| The page takes 5+ seconds to load | You're re-parsing the sheet per request | Cache, or load into SQLite |
| The spreadsheet is too large | Memory pressure, slow scans | Move into SQLite — the sheet stays as source of truth, the DB is the working copy |
| The question depends on yesterday's data | You need a refresh story | Add a scheduled pull, even if it's a manual script for now |
| Someone else wants to ask their own questions | The page is your interface; they want to type | Add an LLM-writes-SQL chat box over the model |
Don't pre-empt these. Wait until one of them is in your face. Friction is the cue — the system tells you when it's ready to grow, you don't decide in advance.
What YAGNI feels like in practice
The discipline is to not build a long list of plausibly-useful things on day one. The list looks like this:
- No database until two sources need joining
- No ORM until there's a schema worth migrating
- No scheduled jobs until "yesterday's data" is no longer good enough
- No auth until someone outside the team needs to see the page
- No deploy pipeline until "runs on my laptop" stops being an acceptable deploy target
- No charting library until a plain table stops being readable
- No abstractions in the access module until the third caller asks for the same thing
Each of these is a real piece of work. Building them in advance means doing the work, and maintaining the result, and reasoning around them — all before any question has asked for it.
An unused column is not foresight. An unused job is not foresight. An unused abstraction is not foresight. They are clutter the next agent — human or Claude — has to understand before it can help.
What never gets skipped
YAGNI applies to architecture. It does not apply to the loop itself.
| Always | Why |
|---|---|
| Atomic commits | One question per commit. When the answer turns out to be wrong, you can revert exactly the change that produced it. |
| A card per question | The card is the record of what was asked and why. Without it, the architecture is a pile of code with no story. |
| CRAFT prompts | The prompt is the contract between you and Claude. A vague prompt produces a vague answer. |
| The answer is on the page | Not in the chat history, not in a notebook nobody opens. Done means visible. |
| Spot-check the number before acting on it | Claude will confidently produce a wrong join. Before a decision rides on an answer, verify it against a hand-calculated row. |
These are cheap on day one and expensive to retrofit. They pay their way regardless of how small the system is.
So — what's the simplest path?
| Stage | Architecture | Trigger to climb |
|---|---|---|
| 1. First question | Sheet (or API) + Pandas script + app-starter page, JSON between them | — |
| 2. More questions, same shape | Add helpers to the Pandas script | The third repeated query |
| 3. New source enters | A data model in the middle — SQLite + ORM | A question that needs a join |
| 4. Many questions, many sources | The model is the app; the dashboard is one view of it | The model has earned its place |
| 5. Outside the team | Postgres, deploy, auth | A real user who isn't you |
Each rung is a response to a question, not a prediction of one. Claude builds exactly what the next card asks for. The card is the unit. The question is the trigger. The answer is the proof.
That's the whole loop.
Further reading
- A data model in the middle — when and why the architecture grows a database layer
- Agentic engineering — why atomic commits and Linear cards aren't optional, even at the smallest scale
- Kanban process — how Stride uses Linear to keep one card per question