expo-product-explorer Ships a README for You and an AGENTS.md for the Robot
A five-product student app that quietly contains the most honest piece of documentation in modern software: instructions telling an AI agent that everything it knows about Expo is probably wrong.
- The most consequential file in this repository is AGENTS.md, which orders any AI coding agent to distrust its training data and read the current Expo documentation before writing code.
- That rule exists because the repo rides a bleeding-edge stack of Expo 57, React Native 0.86, and React 19.2, where framework knowledge decays faster than any human or model can track it.
- The app itself is a hardcoded five-item list, but the scaffolding around it holds the reusable ideas, especially file-suffix platform seams enforced by the type system.
- Agent-facing documentation is becoming a first-class artifact, and every repository will soon have two audiences: the human reading prose and the machine reading a directive.
A Rule Written for the Machine
There is a file in this repository that most people will never open. It is not the README. It is AGENTS.md, and it opens with a command that would be absurd in any other context: do not trust your training data.
That sentence is not a style guide. It is a correction for a known failure mode. Language models are trained on a frozen snapshot of the web. Expo, the framework this repo is built on, ships breaking changes every SDK release. Put those two facts together and you get an agent that will confidently write API calls that stopped working two versions ago.
Expo ships breaking changes every SDK release.
Before writing code, read the installed `expo` major version,
fetch the matching versioned docs, and consult
https://docs.expo.dev/llms.txt
Do not trust your training data.
Read the rest of the file and it becomes a small operating manual for a non-human collaborator. Use npx expo install instead of npm install. Keep routes in src/app/. Never hand-edit the ios/ or android/ folders. Run expo lint and tsc --noEmit before declaring a task finished. Each rule maps to a specific way an agent goes wrong.
The question the rest of this article answers is simple. Why does a five-product app need a rule like that?
The Stack That Made the Rule Necessary
Look at package.json and the answer is obvious. This is not a stable stack. It is the leading edge of one, and the leading edge moves.
| Library | Version | Why it matters |
|---|---|---|
| expo | ~57.0.26 | Framework releases break APIs every cycle, and the docs are versioned per SDK. |
| expo-router/unstable-native-tabs | ~57.0.24 | The import path literally contains the word unstable. |
| react | 19.2.3 | New runtime semantics and compiler behavior. |
| react-native | 0.86.3 | Native bridge changes ship fast and often. |
| typescript | ~6.0.3 | Strict mode and literal types shape what the code can even express. |
| reactCompiler (experiments) | enabled | Automatic memoization changes what correct code should look like. |
Expo publishes docs.expo.dev/llms.txt, a machine-readable index of its documentation, precisely so agents can fetch current truth instead of guessing. The AGENTS.md rule is a pointer to that index. It is the repository admitting that no one, human or model, can hold this stack in their head.
Underneath, a Five-Item List
Strip away the agent docs and you find the app. It is small, and it is honest about being small.
const products: Product[] = [
{ id: '1', name: 'Wireless Headphones', price: 59.99 },
{ id: '2', name: 'Mechanical Keyboard', price: 89.5 },
{ id: '3', name: 'USB-C Hub', price: 34.0 },
{ id: '4', name: 'Desk Lamp', price: 22.75 },
{ id: '5', name: 'Laptop Stand', price: 41.2 },
];
products is a module-level constant. There is no fetch, no store, no API. A FlatList renders five rows, each price formatted with toFixed(2). The header shows a name and a roll number. This is a course project, and it reads like one.
The gap between the name and the code is where the tell-tale details live. A few things are built and then never used:
app-tabs.tsxandapp-tabs.web.tsxare two fully working tab bars that no screen imports.- The root layout is named
TabLayoutbut renders<Slot />, not tabs. - The CI workflow runs
npm installand stops, nevertscorexpo lint. getDevMenuHint()is defined and never called.ThemedViewacceptslightColoranddarkColorprops and ignores both.
None of this is a scandal. It is the normal residue of starting from a template and running out of time. But it sharpens the point. The interesting engineering in this repo is not the feature. It is the scaffolding.
The One Idea Worth Stealing
The genuinely good idea is how the repo handles platform differences. Instead of scattering Platform.OS === 'web' checks through every component, it splits a component into two files and lets the bundler pick the right one at build time.
Three pairs do this: app-tabs against app-tabs.web, animated-icon against animated-icon.web, and use-color-scheme against use-color-scheme.web. Application code imports the bare path. The bundler resolves it, and native-only dependencies never enter the web bundle.
The seam is held together by a piece of type design that is easy to miss.
export type ThemeColor =
keyof typeof Colors.light & keyof typeof Colors.dark;
That intersection takes the keys shared by the light and dark palettes. If a token exists in light mode but not in dark, it is not a ThemeColor, and any component that uses it fails to compile. The two implementations are kept in agreement by the type system rather than by discipline. It is a small idea with a large payoff, and it is the thing a reader can carry into their own project.
Two Ways to Keep an Agent Current
The comparison here is not this repo against a competitor. There are no competitors. It is two postures toward the same problem: how a codebase tells a contributor what is true right now.
| Convention | The AGENTS.md posture | |
|---|---|---|
| Audience | Humans reading the README | Agents writing the code |
| Source of API truth | The model's training data | Versioned docs fetched at task time |
| Failure mode | Agent ships stale API calls | Agent reads current docs first |
| Verification | "Looks right" | expo lint and tsc --noEmit |
| Drift handling | Manual README updates | An explicit rule to distrust memory |
The repository quietly holds both postures at once. The README explains the project to a person. AGENTS.md explains it to the thing that will actually write the code, and it assumes that thing is wrong until it checks.
What This Pattern Implies
This repository has one contributor, a handful of commits, and no community. Do not read it as a phenomenon. Read it as a specimen.
Every repository is about to have two audiences. The first reads prose and wants to understand. The second reads a directive, then goes looking for the current version of the truth. AGENTS.md is an early, imperfect example of documentation written for the second one.
The app will be forgotten. The rule is the part worth keeping.





