Agentic development with Cursor, Expo, and explicit rules
On straightforward web projects I am happy with the stock setup: conventions are clear, the stack nudges you toward consistency, and I do not feel the need for a heavy rules layer. Expo and React Native are different: more surface area (native tooling, navigation, i18n), more ways to drift, and more cost when structure falls apart. So I lean on Cursor not only as an editor, but as a place where agent rules and project docs encode how I want code and copy to behave.
This post is about that split: light touch on the web, explicit, reusable rules on Expo, and how “agentic” development for me means the model follows constraints you wrote, not vibes.
What I mean by agentic (here)
Agentic in this context is simple: the AI has a defined role, checklists, and stop points. It generates or edits code against your rules, stops for review when you say so, and can repeat the same workflow on the next feature. That is replicable across repos if you extract the same AGENTS-style or .cursor/rules patterns.
Web: I keep it light
For marketing or content-led sites, defaults plus docs are enough for me. I rely on framework guidance, linting, and code review rather than a large parallel rulebook. If you are only shipping web and your team agrees on patterns, you may never need the depth of rules I use on mobile. That is a feature, not a weakness.
Expo: rules as architecture
In my Expo app I want one package manager, one directory story, one testing story, and one voice for copy. Cursor rules and markdown living docs make that portable: new contributors (human or model) read the same constraints.
Below are the two pillars I use every day: project agent rules (code + tests + i18n) and product copy rules (voice, punctuation, glossary). They are written as references you can paste, trim, or split into .cursor/rules/*.mdc as you prefer.
Agent rules (Expo project)
The following is the project-level guidance I use for AI-assisted work: stack, yarn-only, src/ layout, module shape, style, test-first with a review gate, and testing expectations.
# Agent Rules
Guidance for AI-assisted development in this project. Follow these rules when generating or modifying code.
## Project overview
- **Stack**: React Native (Expo SDK 55), TypeScript, expo-router
- **Package manager**: See [Package Manager](#package-manager) below.
## Package Manager
- **Always use yarn** for all commands (install, run, add, etc.). Never use npm.
## Directory Structure
**All application code lives in `src/`.** Place new modules, components, and utilities under `src/`.
Use the following top-level directories:
- `src/components/`
- `src/hooks/`
- `src/utils/`
- `src/app/`
- `src/ai-prompts/`
- `src/db/`
- `src/i18n/`
- `src/providers/`
- `src/services/`
- `src/tasks/`
- `src/constants/`: app-wide constants (colors, theme, feature config)
Each component, hook, util, or view lives in its **own directory** with these files:
| File | Description |
|------|-------------|
| `index.tsx` or `index.ts` | Main implementation (use `.tsx` when JSX is required, `.ts` otherwise) |
| `constants.ts` | Constants used by the module (optional) |
| `index.test.tsx` or `index.test.ts` | **Required** test file; every component, hook, and util must have matching tests |
**Example – a Button component:**
```
src/components/
Button/
index.tsx
constants.ts
index.test.tsx
```
**Example – a useCounter hook:**
```
src/hooks/
useCounter/
index.ts
constants.ts
index.test.ts
```
## Coding style
### General
- Use **functional components** with hooks. No class components.
- Prefer **named exports** for components and utilities.
- Use **double quotes** for strings in TS/TSX.
- Use **path aliases** (`@/components/...`, `@/hooks/...`, etc.) instead of relative imports where it improves readability.
### TypeScript
- Prefer explicit types for function params and return values where it helps clarity.
- Use `type` for object shapes; use `interface` when you need declaration merging.
- Avoid `any`; use `unknown` with type guards when the type is uncertain.
### React / React Native
- Extract reusable logic into custom hooks (`useXxx`).
- Keep components focused; split when they exceed ~150–200 lines.
- Use `useCallback` and `useMemo` where they reduce unnecessary re-renders.
- Colocate styles with components via `StyleSheet.create` in the same file.
### i18n
- All user-facing strings must use `t("key")` from `useTranslation()`.
- Add new keys to `src/i18n/locales/en.json` (and other locales if present).
- Use nested keys for structure, e.g. `settings.dataCollection.openSettings`.
- For **wording and voice**, follow [docs/COPY.md](./COPY.md) and the Cursor rule `.cursor/rules/copy.mdc` when editing locale files.
## Code generation
- Generate code that matches the style of the surrounding file.
- Prefer existing primitives (`Card`, `Text`, `Screen`, etc.) over new generic components.
- When adding features, check for similar patterns in the codebase and follow them.
- For AI prompts in `src/ai-prompts/`, always use multi-line template literals (see `.cursor/rules/ai-prompts.mdc`).
### Test-first workflow with review gate
When generating **new** code (features, modules, or significant changes):
1. **First phase (tests only):** Create the co-located test file (`index.test.ts` or `index.test.tsx`) with test cases that outline the expected behaviour. Write clear `describe` and `it` blocks that document inputs, outputs, and edge cases. Do **not** implement the functional code yet.
2. **Pause for review:** Stop and present the test cases to the user. Ask them to review and confirm before proceeding. Example: *"I've added test cases for [X]. Please review them. Once you approve, I'll implement the code to make them pass."*
3. **Second phase (implementation):** After the user confirms, implement the functional code in `index.ts` or `index.tsx` so all tests pass.
4. **Verification:** Run `yarn test` and ensure the test suite is green.
This workflow applies when creating new modules or adding new behaviour. For small edits, bug fixes, or refactors to existing code, you may implement directly and add or update tests as needed.
## Project-Specific Testing Instructions
- **Components must have matching tests:** Every component, hook, and util must have a co-located `index.test.tsx` (or `index.test.ts`) that exercises its behaviour. No module should exist without a corresponding test file.
- **Tools:** Use [Jest](https://jestjs.io) and [React Testing Library](https://testing-library.com) for all unit and component tests.
- **Co-located Tests:** Always create test files in the **same directory** as the file being tested. Never use a separate `__tests__` or `test/` directory. For `index.tsx`, the test file is `index.test.tsx` in the same folder.
- **Test-First:** When implementing a new feature or fixing a bug, first create a failing test case in the co-located test file for that module.
- **Running Tests:** Use `yarn test` from the project root to run the test suite. To run a specific test file, use `yarn jest path/to/your/file.test.tsx`.
- **Verification:** Ensure all tests pass (`code until green`) before considering a task complete.
- **E2E Testing:** For end-to-end tests, use [Playwright](https://playwright.dev) and store tests in the `e2e/` directory.
### Types of Tests in Expo
Expo projects typically use a combination of testing types:
- **Unit Testing:** Tests individual functions, hooks, or small units of code in isolation.
- **Component Testing:** Focuses on rendering React components, their props, state, and user interactions.
- **Integration Testing:** Verifies how different units or components work together as a group.
## Add your own rules
Add project-specific rules below, or create new `.cursor/rules/*.mdc` files for scoped guidance (e.g. file patterns, coding standards per area).
Product copy (voice and conventions)
Copy lives in locale files, but rules keep tone, punctuation, and naming stable when AI suggests strings. I keep a single reference (and point .cursor/rules/copy.mdc at it) so prompts and UI stay aligned.
# Product copy (voice & conventions)
Living document for **user-facing English** in the app. The agent rule [`.cursor/rules/copy.mdc`](../.cursor/rules/copy.mdc) points here.
## Audience
- People **tracking their mood day to day**: not clinicians or a medical audience.
- Copy should feel **calm and collected**: steady, reassuring, never alarmist or jargon-heavy.
## Voice
- **Casual**: contractions are fine (`don't`, `it's`, `you're`) when they sound natural.
- **Clear and direct**: short labels; full sentences where explanation helps.
- **Supportive, not clinical**: acknowledge the user's experience; avoid cold or diagnostic wording.
- **Honest about tradeoffs**: permissions, data, and destructive actions say what happens in plain language.
- **Second person** for instructions and explanations ("you", "your") where natural.
## Punctuation
- **Do not use the em dash (—).** Prefer a period, comma, colon, or parentheses. If a long dash is unavoidable, use a **spaced hyphen** (` - `) instead, never a tight em dash.
## Product name
- Always use **Lutino** when the app or product is named in user-facing copy (dialogs, marketing strings, backup titles, errors that mention the app, etc.).
## Conventions (match and extend `en.json`)
| Context | Pattern | Examples |
|--------|---------|----------|
| Screen / section titles | Title Case | `Settings`, `Data collection` |
| Settings row labels | Title Case | `Enable Notifications`, `Allow Deleting Entries` |
| **Buttons and chip-style actions** | **Title Case** | `Save Entry`, `Open Settings`, `Delete`, `Start Fresh` |
| Body copy, alerts, descriptions | Sentence case; end with punctuation | Permission messages, `*Description` keys |
| Destructive confirms | State consequence + "cannot be undone" when true | Match `journalEntry.deleteMessage` pattern |
- Use **curly apostrophes** in new copy where appropriate (`What's`, `don't`).
- Prefer **one idea per sentence** in helper text under toggles.
## Glossary
| Prefer | Avoid / notes |
|--------|----------------|
| **Entry** (singular), **Entries** (plural) for items the user logs in the journal | Redundant "journal entry" / "journal entries" when "entry/entries" is clear in context |
| **Lutino** for the product name | Generic "the app" when naming the product is clearer |
| **Journal** as the **screen or area** name (e.g. tab title) is fine | Don't use "journal" to mean a single logged item when **Entry** is meant |
- In **labels and buttons**, use Title Case: e.g. "Delete Entry", "Add Entry".
- In **body copy**, use normal sentence case: e.g. "You haven't added any entries yet."
## i18n
- All UI strings go through `t("…")` and live in `src/i18n/locales/en.json` (then other locales as needed).
- Use **nested keys** consistent with siblings, e.g. `settings.dataCollection.openSettings`.
- **Key names** may keep `journal` for history or code structure; **English string values** should follow this glossary.
## AI prompts
- In-app **AI prompts** for models live under `src/ai-prompts/` and follow [`.cursor/rules/ai-prompts.mdc`](../.cursor/rules/ai-prompts.mdc). Tone should still feel **calm, casual, and compatible** with this doc unless the owner intentionally separates assistant voice from UI voice. **Do not use em dashes in prompts** (same rule as product copy).
## Changelog
- **Product owner:** audience, formality, Lutino, Entry/Entries glossary, Title Case buttons (voice locked for day-to-day mood tracking; calm/casual; always Lutino; entries terminology; buttons Title Case).
- **Full pass:** `en.json` and iOS/Android permission strings in `app.json` updated to match this doc (calm/casual copy, Lutino naming, Entry/Entries, Title Case actions, curly apostrophes).
- **Punctuation:** no em dash in UI or prompts; use `. , : ( )` or spaced ` - ` if needed.
Generic agent script (Cursor)
Save as something like .cursor/agent-scripts/expo-default.md or split into rule files. This script summarises the agent rules and copy rules above so a single prompt bootstraps behaviour for a new task or session.
# Expo + Lutino: default agent behaviour
You are assisting on a React Native (Expo SDK 55) app with TypeScript and expo-router.
## Non‑negotiables
- **Package manager:** use **yarn** only (never npm).
- **Code location:** all app code under **`src/`**. New modules live in named folders with `index.ts` or `index.tsx`, optional `constants.ts`, and **required** co-located `index.test.ts` or `index.test.tsx`.
- **Style:** functional components, named exports, double quotes, path aliases where helpful. No class components. Prefer `type` for shapes; avoid `any`.
- **UI strings:** every user-visible string uses `t("…")` from `useTranslation()`; new keys go in `src/i18n/locales/en.json` with nested structure.
- **Copy:** follow product copy rules: calm, casual, supportive; **no em dashes** in UI or prompts (use `. , : ( )` or spaced ` - `). Product name **Lutino**. **Entry / Entries** glossary. Buttons and chip actions in **Title Case**.
- **AI prompts** under `src/ai-prompts/` use multi-line template literals per `.cursor/rules/ai-prompts.mdc`.
## New feature workflow (review gate)
1. **Tests first:** add or extend the co-located test file only. Describe behaviour with `describe` / `it`; cover edge cases. Do **not** implement production code yet.
2. **Stop:** ask the human to review tests. Example: "I've added tests for [X]. Approve and I'll implement until green."
3. **Implement:** after approval, implement `index.ts` / `index.tsx` until `yarn test` passes.
4. For **small fixes** or **refactors**, you may change code and tests together; still keep tests co-located and run `yarn test` before finishing.
## Quality bar before you stop
- Co-located tests exist for touched components, hooks, and utils.
- `yarn test` green.
- Strings and tone match copy rules; no em dashes in user-facing text or prompts.
- Prefer existing app primitives and patterns over inventing new abstractions.
Why this pays off
When rules and scripts are versioned with the repo, Cursor stops being a clever autocomplete and becomes a consistent teammate: same layout, same test discipline, same voice in en.json. The web side can stay light; Expo gets the guardrails it needs. If you adopt one idea from this post, make it the review gate: tests first, human approves, then implementation. Everything else is easier after that.