Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .agents/skills/pr-description/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: pr-description
description: Draft a concise pull request description for this React Native Expo template. Use when the user asks to write, generate, update, or prepare a PR description, PR body, pull request summary, or GitHub pull request text from the current branch changes.
---

# PR Description

## Purpose

Create a brief, reviewer-friendly PR description from the current branch diff. The output must follow the repository's pull request template exactly, preserving every heading and keeping placeholders where content is unknown.

## Style Rules

- Use plain language. Avoid filler like "this PR aims to", "in order to", and "leverages".
- Be concrete. Name the screens, components, flows, commands, or files that changed.
- Keep it short. Each prose section should usually be 1-3 sentences.
- Avoid bullet soup. If a section needs more than 6 bullets, group related items.
- Skip obvious filler such as import cleanup or lint-only noise unless that is the point of the PR.
- Do not invent Jira tickets, screenshots, devices tested, or implementation details not supported by the diff.

## Workflow

1. Read the repository instructions first:
- `AGENTS.md`
- `REVIEW.md` when present, because this repo requires it for review-related work.
2. Find the PR template:
- Prefer `.github/PULL_REQUEST_TEMPLATE.md`.
- Fall back to `PULL_REQUEST_TEMPLATE.md` at the repo root only if the GitHub template is absent.
3. Refresh and inspect the branch:
- Run `git fetch origin master`.
- Run `git log origin/master..HEAD --oneline`.
- Run `git diff origin/master...HEAD --stat`.
- Run `git diff origin/master...HEAD`.
4. Draft the PR description using the exact headings and order from the template.
5. Save it as `PR_DESCRIPTION.md` at the repo root, overwriting any existing file.
6. Ensure `PR_DESCRIPTION.md` is ignored by git. If it is missing from `.gitignore`, append it under:

```gitignore
# PR description scratchpad (agents)
PR_DESCRIPTION.md
```

7. Report back with the relative path only, unless the user asks to see the content.

## Section Guidance For This Repository

The current template is `.github/PULL_REQUEST_TEMPLATE.md`. Keep the warning comment and separators from the template if they are present.

### Jira board reference:

Leave the example placeholder or replace it only if a real Jira ticket is present in the branch name, commits, or user request. Do not invent ticket IDs.

### What does this do?

Write 2-4 concise sentences describing what changed. Focus on user-facing behavior, developer workflow changes, or the concrete problem fixed.

### Why did you do this?

Write 1-3 concise sentences explaining the reason for the change. Tie it to the bug, product need, maintainability concern, or template behavior visible in the diff.

### Who/what does this impact?

Name affected screens, flows, platforms, users, modules, or developer workflows. Use a short sentence or compact bullets. If impact is unclear, leave a placeholder comment instead of guessing.

### How did you test this?

List commands run and manual checks performed. Keep the existing checklist from the template and check only items that are supported by evidence or explicitly provided by the user.

### Notes:

Include caveats, follow-ups, migration notes, or review hints. If there are none, leave the template placeholder.

### Screenshots / Previews

Keep the screenshot/Figma/device placeholders unless real images or recordings are available. Remove irrelevant bug before/after tables only when the template structure still remains clear.

## Final Response

After writing the file, say where it was saved, for example:

`Done: PR_DESCRIPTION.md`

Do not paste the full PR description unless the user asks.
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "pnpm type-check 2>&1 | head -30"
}
]
}
]
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ expo-env.d.ts

# cli
cli/README.md

# PR description scratchpad (agents)
PR_DESCRIPTION.md
52 changes: 52 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# AGENTS.md

This is a React Native + Expo template maintained by Rootstrap. It provides a production-ready starting point for mobile apps targeting iOS, Android, and Web. It is used as the base for client projects and internal tooling at Rootstrap.

When reviewing code, read and apply the rules in ./REVIEW.md

---

## Glossary

| Term | Definition |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AuthProvider` | React Context provider in `src/components/providers/auth.tsx` — manages reactive auth state for the UI layer |
| `useAuth` | Hook to consume `AuthProvider` context (UI layer) OR Zustand store selector from `src/lib/auth/index.tsx` (imperative layer) — do not confuse them |
| `hydrateAuth` | Called once at app startup (`src/app/_layout.tsx`) to rehydrate Zustand auth state from MMKV storage |
| `TokenType` | Shape of the Devise Token Auth token object: `{ bearer, access, client, uid, expiry }` — defined in `src/lib/auth/utils.tsx` |
| `HEADER_KEYS` | Constant map of Devise Token Auth header names (`access-token`, `client`, `uid`, `expiry`, `Authorization`) — defined in `src/components/providers/auth.tsx` |
| `isFirstTime` | Boolean persisted in MMKV via `src/lib/hooks/use-is-first-time.tsx` — controls whether the onboarding screen is shown |
| `Env` | Runtime environment config imported from `@/lib/env` (populated via `expo-constants`) — the only safe way to read env values |
| `PaginateQuery<T>` | Paginated API response shape: `{ results: T[], count, next, previous }` — defined in `src/api/types.ts` |
| `normalizePages` | Utility in `src/api/common/utils.tsx` that flattens TanStack infinite query pages into a flat array for FlashList |
| `queryFactory` | Merged query key registry built with `@lukemorales/query-key-factory` — defined in `src/api/query-factory.ts` |
| `createMutation` | Factory from `react-query-kit` for creating typed mutation hooks — required pattern for all mutations |
| `createQuery` | Factory from `react-query-kit` for creating typed query hooks — required pattern for all queries |

---

## Critical constraints

- **Package manager**: Always use `pnpm`. Never suggest `npm install` or `yarn`. Enforced via `preinstall` hook.
- **Env values**: Never hardcode environment-specific values. Always use `Env` from `@/lib/env`.
- **Auth logic in screens**: Never add authentication state or redirect logic inside screen components. Auth state lives in `AuthProvider`; routing guards live in `src/app/_layout.tsx` via `Stack.Protected`.
- **Case transformation**: Never add manual camelCase↔snake_case conversion. The Axios interceptors in `src/api/common/interceptors.ts` handle this automatically for all requests and responses.
- **MMKV access**: Never call `storage` (from `src/lib/storage.tsx`) or `authStorage` directly inside components. Use the exported helper functions (`getItem`, `setItem`, `removeItem`, `storeTokens`, `getTokenDetails`, `clearTokens`).
- **Strings**: All user-facing strings must use `useTranslation()`. Every key must exist in `src/translations/en.json`.
- **API hooks**: New hooks must use `createMutation` or `createQuery` from `react-query-kit`. Never use raw `useMutation` or `useQuery` from TanStack directly.
- **Query keys**: Every new query domain must be registered in `src/api/query-factory.ts` using `createQueryKeys` from `@lukemorales/query-key-factory`.
- **Lists**: Use `@shopify/flash-list` for any list that can grow. Never use `FlatList` for feed-style content.
- **Crypto/IDs**: Use `expo-crypto` for random IDs. Never use `Math.random()` or `Date.now()` as identifiers.
- **New dependencies**: Before installing any library, verify the latest version compatible with this stack — React Native 0.81, Expo SDK 54, and React 19. Check compatibility via `npx expo install <package>` (which resolves the Expo-blessed version) or the package's peer dependencies. Do not install the latest npm version blindly — it may not support this SDK version.

---

## Table of contents

| File | Description |
| ---------------------------- | -------------------------------------------------------------------------------- |
| `agent_docs/architecture.md` | Layer diagram, folder structure, routing conventions, dual auth system explained |
| `agent_docs/conventions.md` | API hook patterns, styling, forms, i18n, anti-patterns |
| `agent_docs/commands.md` | All pnpm commands for dev, test, lint, build, and setup |

## Imported Claude Cowork project instructions
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Strictly follow the rules in ./AGENTS.md
67 changes: 67 additions & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# REVIEW.md

Code review guidelines for human teammates and AI agents. All categories are grounded in patterns found in this codebase.

---

## 1. API & Query layer

- New hooks must use `createMutation` or `createQuery` from `react-query-kit` — never raw `useMutation` / `useQuery` from TanStack directly.
- Every new query domain must be registered in `src/api/query-factory.ts` using `createQueryKeys`. Query keys must not be hand-rolled strings.
- Requests that send a body should wrap the payload as `{ user: <variables> }` (established in `src/api/auth/use-login.ts` and `src/api/auth/use-sign-up.ts`). Flag any deviation from this pattern and confirm with the team whether it's intentional for that endpoint.
- Paginated endpoints must type their response as `PaginateQuery<T>` (see `src/api/types.ts`). Infinite list consumers must use `normalizePages()` from `src/api/common/utils.tsx` to flatten pages before passing data to FlashList.
- Never add manual camelCase↔snake_case conversion in hooks or components — the interceptors in `src/api/common/interceptors.ts` handle this automatically.

---

## 2. Auth & security

- Auth state and token management must not live in screen components. Business logic belongs in `src/components/providers/auth.tsx`; routing guards belong in `src/app/_layout.tsx`.
- Tokens must be stored exclusively via `storeTokens()` and read via `getTokenDetails()` from `src/components/providers/auth.tsx`. Never store auth tokens in plain React state, `AsyncStorage`, or the general `storage` instance from `src/lib/storage.tsx`.
- All routes that require a logged-in user must be inside the `(app)/` group, wrapped in `Stack.Protected` with `guard={isAuthenticated}` in `src/app/_layout.tsx`. Never replicate this guard inside individual screen files.
- Verify that API calls are not triggered before `ready === true` from `AuthProvider`. Until `ready` is true, the auth state is not yet hydrated from MMKV.
- Token expiry must be evaluated using `dayjs` comparison against the ISO string stored in `authStorage` — do not compare raw Unix timestamps or use `Date` directly.

---

## 3. Type safety

- All API hook `Variables` and `Response` types must be explicitly defined in the same file as the hook. No `any`, no `unknown` without a type guard.
- Zod validation schemas must drive TypeScript types via `z.infer<typeof schema>` — never define the type separately and then write a matching schema; the schema is the source of truth.
- Environment variables must be accessed through `Env` from `@/lib/env`. Never use `process.env` or `Constants.expoConfig?.extra` directly in feature code.
- Axios response/request types must use the types from `src/api/common/axios.d.ts` augmentation where applicable.

---

## 4. i18n

- No hardcoded English (or any language) strings in JSX. Every user-facing string must be retrieved with `useTranslation()`.
- Every new key added in a component must have a corresponding entry in `src/translations/en.json`. Run `pnpm lint:translations` to verify.
- Avoid constructing translation keys dynamically (e.g., ``t(`error.${code}`)``) unless the full key set is exhaustively defined in the translations file.

---

## 5. Styling

- Use NativeWind Tailwind utility classes on React Native primitives. No `StyleSheet.create` except for top-level layout containers (e.g., `flex: 1` wrappers) where Tailwind classes are insufficient.
- Components with multiple visual variants must use `tailwind-variants` — avoid conditional className string concatenation with ternaries or template literals.
- Do not mix NativeWind class-based styling with inline `style` props on the same element unless there is no Tailwind equivalent.

---

## 6. Testing

- Every new component in `src/components/` must have a corresponding test file in `__tests__/components/`.
- Always import `render`, `screen`, `fireEvent`, and `cleanup` from `@/lib/test-utils` (re-exports from `src/lib/test-utils.tsx`) — never import them directly from `@testing-library/react-native`. The custom `render` wraps components with required providers.
- Form component tests must cover at minimum: (1) renders correctly, (2) shows required-field errors on empty submit, (3) shows format validation error on invalid input. See `__tests__/components/login-form.test.tsx` as the canonical reference.
- Native module mocks live in `__mocks__/`. Before adding a new mock, check existing ones (e.g., `react-native-mmkv`, `react-native-gesture-handler`, `moti`) to avoid duplication or conflicts.
- Use `testID` props for interactive elements queried with `getByTestId`. Follow the naming pattern in existing components (e.g., `login-button`, `email-input`, `password-input`).

---

## 7. Performance

- Use `@shopify/flash-list` for any list that can grow (feeds, search results, infinite scroll). Never use `FlatList` for these cases.
- Do not call API query hooks inside top-level layout components (`_layout.tsx`) if only one child screen actually needs that data. Fetch at the screen level.
- Side effects triggered by user actions (form submit, button press) must go inside mutations — not in `useEffect` watching state changes. `useEffect` is for synchronising with external systems, not for reacting to user interactions.
- Avoid anonymous functions or inline object literals as props to FlashList's `renderItem` and `keyExtractor` — use stable references with `useCallback`.
Loading
Loading