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.

6 min read • View on GitHub • More from NoorUlBaseer

An editorial illustration of a drafting table seen from a three-quarter angle. On the left sits an open, worn manual with dog-eared pages and hand-annotated margins. On the right, a small faceless brass clockwork arm hovers over a blank sheet. A single taut wire runs from the open book to the arm's wrist joint. The image stands for an AGENTS.md file feeding current documentation to a coding agent before it writes anything.
The manual on the left is AGENTS.md. The arm on the right is the coding agent. The wire between them is the instruction to fetch current docs instead of trusting memory.
Key Takeaways

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.

LibraryVersionWhy it matters
expo~57.0.26Framework releases break APIs every cycle, and the docs are versioned per SDK.
expo-router/unstable-native-tabs~57.0.24The import path literally contains the word unstable.
react19.2.3New runtime semantics and compiler behavior.
react-native0.86.3Native bridge changes ship fast and often.
typescript~6.0.3Strict mode and literal types shape what the code can even express.
reactCompiler (experiments)enabledAutomatic 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:

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.

An editorial illustration showing a single component drawn as a small Greek temple front, split down the center. The left half is rendered in solid engraved line and labeled .tsx, the right half as a faint dotted outline of the same shape labeled .web.tsx. A single hand at the bottom of the frame holds both halves and selects one. The image explains how a bundler swaps one file for another based on its suffix.
One import path, two implementations. The bundler chooses at build time, and the shape stays identical on both sides.

One import path resolves to two files. The bundler flips the switch, and the type system keeps both sides honest.

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.

ConventionThe AGENTS.md posture
AudienceHumans reading the READMEAgents writing the code
Source of API truthThe model's training dataVersioned docs fetched at task time
Failure modeAgent ships stale API callsAgent reads current docs first
Verification"Looks right"expo lint and tsc --noEmit
Drift handlingManual README updatesAn 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.