6.5 KiB
6.5 KiB
AGENTS.md
Guidance for AI coding agents working in this repository.
Project overview
LNReader is a free, open-source light novel reader for Android, built with React Native (Expo) and TypeScript. Content is fetched at runtime by JavaScript "plugins" (per-source scrapers) rather than bundled with the app. Plugin requests live in a separate repo, lnreader-plugins; this repo is the reader app itself.
Setup
pnpm install
- Node >= 22.11.0, pnpm (see
packageManagerinpackage.json, currently pnpm@11.20.0). - This is a React Native/Expo app targeting Android; there is no web or iOS build target maintained here. Building/running the app requires an Android SDK and a device/emulator, which is generally not available in an agent sandbox — prefer running lint, type-check, and tests to verify changes.
- See
CONTRIBUTING.md(andCONTRIBUTING-NIX.mdfor a Nix-based dev shell) for full local Android setup instructions.
Commands
Run these from the repo root:
pnpm run lint # eslint ./src
pnpm run lint:fix # eslint --fix
pnpm run format:check # prettier --check
pnpm run format # prettier --write
pnpm run type-check # tsc --noEmit
pnpm run check # format:check + lint + type-check (run before finishing any change)
pnpm test # jest (all projects)
pnpm run test:rn # jest --selectProjects rn (React Native/component/hook tests)
pnpm run test:db # jest --selectProjects db (database/query tests, node env)
pnpm run test:watch
pnpm run test:coverage
Always run pnpm run check (or at minimum lint + type-check + the relevant
test project) before considering a change complete. A pre-commit hook
(husky + lint-staged) runs prettier/eslint on staged files, and a
pre-push hook runs type-check, so failures here will also block commits/pushes.
Code style
- TypeScript, functional React components, hooks-based state.
- Formatting is enforced by Prettier (
.prettierrc.js) — don't hand-format, runpnpm run formatinstead. - ESLint config is
eslint.config.js(flat config, based oneslint-config-expo). Notable rules:no-consoleis an error (use the project's logging/toast utilities, notconsole.*),prefer-const,no-var, and severalreact-hooks/*correctness rules are enforced. - Use the
@alias/*path aliases defined intsconfig.json(and mirrored injest.config.js) instead of deep relative imports, e.g.@components,@hooks,@database/*,@screens/*,@services/*,@utils/*,@api/*,@theme/*,@plugins/*,@navigators/*,@i18n/*,@type/*. - Don't add comments that just restate what the code does; only comment non-obvious "why" (workarounds, invariants, native/platform quirks).
Localization (i18n)
src/i18n/languages/en/strings.jsonis the source-of-truth string file — it's the only locale you should hand-edit. Every other folder undersrc/i18n/languages/*is a translation synced automatically from Crowdin (seecrowdin.yml); don't edit or "fix" translated strings in those files.- After adding/changing keys in
en/strings.json, runpnpm run generate:string-typesto regenerate the associated TypeScript types.
Repository layout
src/api– external API clients (drive/cloud backup providers, etc.)src/components– shared/reusable UI componentssrc/database– SQLite (op-sqlite/Drizzle) schema, queries, and migrations helpers;src/database/queries/*Queries.tsare the main data-access layersrc/hooks– shared and "persisted" (MMKV-backed) React hookssrc/i18n– translated strings (see Crowdin integration incrowdin.yml)src/navigators– React Navigation stack/tab setupsrc/plugins– plugin manager and plugin type definitions (not the plugin source scrapers themselves, which live in the separate lnreader-plugins repo)src/screens– feature screens (library, browse, novel, reader, settings, etc.)src/services– background tasks, downloads, backups, trackers, EPUB export, updatessrc/theme– Material3 themingsrc/utils– general utilitiesdrizzle/– generated SQL migrations (seepnpm run generate:db-migration)modules/– native modules (Nitro modules: epub, tts, file, zip-archive, etc.)test/– Jest global mocks and the sharedtest-utils.tsxrender helpersscripts/– Node scripts for env file generation, release packaging, etc.
Testing notes
- Don't add tests for the sake of coverage. Write tests for user-facing interactions (screens, components a user directly touches) and for core complex logic (parsing, database queries, sync/backup, chapter/novel matching, etc.), not for trivial passthroughs, simple type/prop plumbing, or generated/boilerplate code.
- Tests live in
__tests__directories next to the code under test and use Jest +@testing-library/react-native. - Jest is split into two projects (see
jest.config.js):db— pure logic/query tests undersrc/database, run in a Node environment.rn— component/hook/integration tests undersrc/, run with thejest-expopreset.
- Import the shared render helpers from
@test-utils(render,renderNovel,AllTheProviders) rather than using@testing-library/react-native'srenderdirectly, so providers (theme, gesture handler, safe area, novel context) are set up consistently. - Mock at module level with
jest.mock(...), not insidebeforeEach— seeTESTING.mdfor the reasoning and common pitfalls (ESM transform errors,mockReturnValuefailures, test isolation). - Global mocks for native modules and database queries live in
test/mocks/; hook-specific mocks live insrc/hooks/__mocks__andsrc/hooks/__tests__/mocks.ts.
Database changes
- Schema lives under
src/database/schema; the ORM is Drizzle (drizzle-orm, config indrizzle.config.ts). - After changing the schema, generate a migration rather than hand-writing
SQL:
pnpm run generate:db-migration. Do not edit files underdrizzle/by hand.
PR / commit conventions
- Keep changes scoped and consistent with existing patterns in the touched directory; prefer editing existing files over introducing new abstractions.
- Match existing code style; don't reformat unrelated code.
- If you are an AI agent opening a PR or making a commit, attribute it as such (e.g. a "Generated with <agent/tool name>" note in the commit message/PR description, and a co-author trailer if your tool supports one).