A Manga and Manhwa reading tracker built with React Native, Expo SDK 57, TypeScript, Expo Router, and TanStack Query. Search the public Kitsu catalog, open a title, save it to your library, track chapter progress and reading status, and organize titles into custom lists.
All saved entries and lists are read and written through an external REST API using Fetch. There is no local backend to run. This workspace includes a configured, public Beeceptor demo endpoint so you can try saving immediately. MockAPI is recommended when you want your own free project with more room than the small Beeceptor demo. API setup and demo limits.
Use Node.js 22.13 or later and npm. This project follows the versioned Expo SDK 57 documentation, which targets React Native 0.86 and React 19.2.3. Exact installed versions are recorded in package-lock.json.
From the original current directory, enter the new, independent app folder:
cd inktrail
npm ci
npm startOpen the development URL with a compatible Expo Go client, or use an Android emulator. Keep your phone and development computer on the same network. Platform shortcuts:
npm run android
npm run webOn macOS with an iOS simulator installed:
npm run iosThe project uses the Expo managed workflow. Native android/ and ios/ project directories are not needed to run it. Catalog browsing works immediately with an internet connection. The local .env connects the delivered workspace to the demo tracker; a fresh copy without that file can connect through the Connection screen.
Import this GitHub repository into Vercel, keep the root directory at the repository root, and select Other as the framework preset. The included vercel.json sets npm ci as the install command, npm run export:web as the build command, and dist as the output directory. Its rewrite lets Expo Router handle direct links and page refreshes, including /library, /connection, and /title/123.
Use Node.js 22.13 or later. To connect the deployed app to a tracker by default, add EXPO_PUBLIC_TRACKER_API_URL in the Vercel project's environment variables before deploying. This is a public HTTPS API base URL, without /entries or /lists. Without it, readers can configure their endpoint from the app's Connection screen. Changes to this environment variable require a new deployment because Expo embeds it during the build. The local .env is excluded from Git and CLI uploads.
Alternatively, deploy from this directory with Vercel CLI:
npx vercel login
npx vercel --prodA dedicated anonymous Beeceptor endpoint was created on October 11, 2026, with both CRUD routes configured:
- API base: ca491042a4a2e9cd0092.free.beeceptor.com
- Dashboard: Inspect requests and manage routes
The local .env points to this endpoint. It is public, holds at most 10 total entries and lists, and allows 50 requests per day. Older records can be purged when full, and an unclaimed endpoint lasts 30 days from creation. Use sample reading data. See the provider's storage behavior and plan limits.
- Follow API_SETUP.md to create the two resources,
entriesandlists, in MockAPI or Beeceptor. - Open the app's Connection screen and paste your provider's HTTPS base URL.
- Connect. The app checks GET
/entriesand GET/listsbefore saving the URL preference. - Search for a title, open its details, save it, and inspect the new record in your API dashboard.
Alternatively, on a fresh copy without .env, configure the URL before starting Expo:
if (-not (Test-Path .env)) { Copy-Item .env.example .env }Edit .env using the exact base URL from your provider:
EXPO_PUBLIC_TRACKER_API_URL=https://YOUR_PROJECT.mockapi.io/api/v1Then stop the running Expo process and run npx expo start --clear to refresh Metro's transformed environment values. The in-app saved connection takes precedence over this environment value. Disconnecting saves an empty connection; reconnect through the screen to select an endpoint again.
Only the endpoint preference is persisted on the device. Entries and lists come from the API, with temporary in-memory caching for the current session. The app never substitutes local saved records when the API is unavailable.
inktrail/
|-- app/
| |-- _layout.tsx # Providers and root stack
| |-- (tabs)/
| | |-- _layout.tsx # Discover and Library tabs
| | |-- index.tsx # Home/Search route
| | `-- library.tsx # User Tracker Library route
| |-- title/[id].tsx # Title Details route
| `-- connection.tsx # External API connection route
|-- src/
| |-- api/
| | |-- http.ts # Fetch, JSON/errors, timeouts, retry policy
| | |-- catalog.ts # Kitsu queries and response mapping
| | `-- tracker.ts # Entry/list REST operations and validation
| |-- components/ui.tsx # Shared UI primitives
| |-- hooks/
| | |-- useCatalog.ts # Catalog pagination and title queries
| | `-- useTracker.ts # Remote library/list queries and mutations
| |-- providers/
| | |-- AppProviders.tsx # Query cache and safe-area context
| | `-- ConnectionProvider.tsx # Endpoint preference and connection state
| |-- screens/
| | |-- HomeScreen.tsx
| | |-- TitleDetailsScreen.tsx
| | |-- LibraryScreen.tsx
| | `-- ConnectionScreen.tsx
| |-- config.ts # Endpoint normalization and storage key
| |-- theme.ts # Shared visual tokens
| `-- types.ts # Catalog, tracker, and reading-list models
|-- tests/api.test.ts # API contract and validation tests
|-- docs/API_SETUP.md # Provider setup, schema, requests, walkthrough
|-- .env.example
|-- app.json
|-- expo-env.d.ts
|-- package.json
|-- package-lock.json
`-- tsconfig.json
Start with src/types.ts. Manga is a normalized catalog title, TrackerEntry adds the provider's record ID, status, chapter progress, and optional list membership, and ReadingList is a named collection.
The tracker uses these reading statuses:
| Stored value | Display label |
|---|---|
reading |
Reading |
planned |
Want to read |
completed |
Completed |
on_hold |
On hold |
dropped |
Dropped |
Chapter progress must be a nonnegative whole number. If a total is known, progress cannot exceed it. Unknown totals stay null, so ongoing titles can continue to gain chapters. A custom list is independent of reading status; a title has at most one custom list.
src/api/http.ts centralizes Fetch. It checks HTTP status, parses JSON, supports abort signals, limits request time, and returns readable errors. GET requests may retry once for temporary failures. Writes are never retried automatically because an interrupted response can hide a successful server write.
src/api/catalog.ts reads Kitsu JSON:API data from https://kitsu.app/api/edge, sending Accept: application/vnd.api+json. Search uses /manga with filter[text], filter[subtype], offset pagination, and include=categories; details use /manga/:id?include=categories. The All filter merges manga and manhwa requests with separate cursors, promoting exact title matches before prefix and contained matches. Requests are spaced about 1.05 seconds apart as a courtesy, without assuming a provider rate-limit guarantee. IDs in routes and saved entries are numeric Kitsu manga IDs. Kitsu API reference.
src/api/tracker.ts handles all persistent data operations against the connected base URL. Create uses POST, reads use GET, edits use PUT with the full record, and removal uses DELETE. Both /entries and /lists have complete CRUD support. Exact payloads and endpoint examples are in the API guide.
AppProviders.tsx owns one QueryClient and the safe-area provider. ConnectionProvider.tsx loads the endpoint preference and exposes connection state. No secret credentials are bundled into the app.
useCatalog.ts exposes useCatalog for paginated results and useTitle for a selected title. useTracker.ts exposes useEntries, useLists, and useTrackerMutations. Screens read server state from these hooks rather than copying it into a second global store. Local React state is reserved for search inputs, filters, and form drafts.
Settled mutations invalidate the affected remote queries, including failures that may have partially committed remotely. Pending actions are disabled, and failures remain visible instead of displaying false success. Endpoint-specific query keys keep one connection's data separate from another's.
Route: app/(tabs)/index.tsx. Complete component: HomeScreen.tsx.
- Keep the search text and All/Manga/Manhwa filter in component state.
- Debounce input before passing it to
useCatalog. - Display popular catalog titles before a search and matching results afterward.
- Render covers and metadata in a responsive catalog grid. Expose loading, empty-result, pagination, and retry states.
- Navigate to
/title/{id}when a title is selected. Keep IDs in the route and retrieve current details through the catalog API.
Route: app/title/[id].tsx. Complete component: TitleDetailsScreen.tsx.
- Read and validate the route's title ID, then request details with
useTitle. - Show its cover, format, score, chapter count when known, genres, and synopsis. Author information is optional; the current Kitsu adapter leaves it empty when not supplied.
- Check
useEntriesfor an existing entry matching the catalog ID. - Save a new entry through POST
/entries, or edit the existing entry's chapters, reading status, and custom list through PUT/entries/:id. - Offer a link to the library, where saved titles can be removed. If the tracker is not configured, direct the reader to Connection.
The external tracker stores a small title snapshot so library cards remain useful without requesting full catalog details for every saved entry.
Route: app/(tabs)/library.tsx. Complete component: LibraryScreen.tsx.
- Fetch entries and custom lists with
useEntriesanduseLists. - Derive visible entries from the selected status, custom list, and local search/filter state.
- Display progress, offer quick chapter updates and an edit dialog, and confirm title removal before DELETE
/entries/:id. Open details for catalog information and the full editor. - Create and rename custom lists through POST/PUT
/lists. - Delete a custom list by first clearing its entries'
listId, then deleting the list. Saved titles remain in the library. - Provide refresh, loading, empty-library, and API-error states so an unavailable service is distinguishable from an empty library.
List deletion spans multiple REST calls. It is not atomic on these mock services; partially completed changes can be refreshed and retried. The guide explains this limitation and the API requirements for a production service.
app/_layout.tsx wraps the stack with providers. The tab layout keeps discovery and library access available, while details and connection have their own stack routes. theme.ts and ui.tsx keep colors, spacing, typography, buttons, inputs, cards, and feedback consistent across screens.
The UI uses safe areas, scrollable content, visible pending/error states, and descriptive action labels. The catalog and library are separate screens so a slow catalog response does not prevent access to the tracker controls.
Run these from inktrail:
npm run typecheck
npm test
npx expo install --check
npx expo-doctor
npx expo export --platform allVerified on October 11, 2026: TypeScript passes, all 14 API tests pass, Expo dependency validation passes, and Expo Doctor passes all 21 checks. Production exports succeed for Android, iOS, and web. A phone-sized browser check verified live catalog search, title details, saving a title, chapter updates, and confirmed removal with no runtime console errors. A separate real Beeceptor check verified entry and list CRUD, including preserving titles when deleting a list; all temporary verification records were removed.
The automated API tests use controlled Fetch responses and do not touch the public endpoint. Android/iOS bundles were compiled; interaction on a physical native device or simulator was not verified here. Follow the manual verification sequence for device testing or a replacement provider.
The delivered project is ready to use with npm ci. For a separate fresh implementation, create an Expo TypeScript project with create-expo-app, then target SDK 57 and install compatible managed libraries:
npx create-expo-app@latest inktrail-fresh --template expo-template-blank-typescript@57.0.29
cd inktrail-fresh
npm install expo@~57.0.27
npx expo install --fix
npx expo install expo-router expo-constants expo-font expo-linking expo-status-bar react-native-safe-area-context react-native-screens react-native-gesture-handler react-native-reanimated react-native-worklets @react-native-async-storage/async-storage @expo/vector-icons react-dom react-native-web
npm install @tanstack/react-query@^5
npm install --save-dev typescript@~6.0.3 @types/react tsx @types/nodeCopy this project's app/, src/, tests/, app.json, tsconfig.json, expo-env.d.ts, and .env.example into that new project. Set package.json's main to expo-router/entry and copy its scripts from this project. The blank template's App.tsx and index.ts are no longer entry points and can be removed. Run the checks above. For identical dependency versions, use this project's package.json and lockfile together, followed by npm ci.
The free tracker endpoint is a shared public demo library, not an authenticated user account. Keep it dedicated to this project and use non-sensitive test data. The app guards form inputs and ordinary duplicate saves, but a real multi-user service must enforce authentication, ownership, uniqueness, concurrent updates, and list transactions on its external API. The included Beeceptor endpoint is a public demonstration; no private account or paid service was created.