Architecture Diagram
Layer Breakdown
1. tarkov.dev GraphQL API (External Data Source)
The application fetches all quest, map, trader, hideout, and item data from the community-maintained tarkov.dev GraphQL API.- Purpose: Provides read-only game data (quests, maps, items, hideout stations)
- Location:
src/api/graphql.ts:1 - Implementation: Single
gqlFetch()function wrapsfetch()with GraphQL error handling
2. TanStack Query (Memory Cache Layer)
API responses are cached in memory only using TanStack Query v5, with a 12-hour stale time and infinite garbage collection time.- Purpose: Minimize API requests, enable offline-first experience
- Location:
src/api/hooks.ts:24 - TTL: 12 hours (
staleTime: TWELVE_HOURS) - Fallback: If online fetch fails, Query falls back to last successful cache
API data is never persisted to IndexedDB. Only user-generated data (progress, pins, notes) is stored locally. This keeps the cache fresh on app updates and avoids schema migration complexity.
3. React Components (UI Layer)
All UI is built with React 18 functional components, organized by feature:components/dashboard/- Kappa progress, Now panel, recommendationscomponents/tasks/- Quest list/flow view, filteringcomponents/map/- Interactive map with user pinscomponents/hideout/- Hideout station construction trackercomponents/items/- Item price tierscomponents/settings/- Player level, data export/import, wipe reset
- API hooks (
useTasks(),useMaps(), etc.) for game data - Zustand stores for UI state (filters, active tab, etc.)
- Domain functions for computed values (unlock state, recommendations)
4. Zustand Stores (UI State Management)
Zustand v5 manages ephemeral UI state only (not persisted):- Active filters (trader, map, status)
- Sidebar collapse state
- Active view mode (list vs flow)
- Tier thresholds for item pricing
Zustand is used only for UI state. User data (task completion, notes, pins) is managed directly via Dexie CRUD operations, not through Zustand.
5. Domain Logic (Business Rules)
Pure TypeScript functions insrc/domain/ encapsulate all game logic:
Unlock Determination (domain/unlock.ts:11)
A task unlocks when:
playerLevel >= task.minPlayerLevel- All prerequisite tasks are marked
"done"
Recommendation Scoring (domain/recommendations.ts:96)
Recommended tasks are scored using a weighted composite formula:
- Downstream impact (50%): # of Kappa tasks unlocked by completing this task
- Map batch efficiency (30%): # of other Kappa tasks on the same map
- Kappa requirement (20%): Binary flag for Kappa-required quests
Kappa Progress (domain/kappaProgress.ts:14)
Calculates overall and per-trader completion percentage for Kappa-required quests.
Next Unlock Preview (domain/nextUnlock.ts:13)
Finds tasks that will unlock in the next N levels (default 5), grouped by required level.
6. Dexie/IndexedDB (Persistence Layer)
All user-generated data is stored in IndexedDB via Dexie v4:profile: Player level, wipe IDprogress: Task completion status (not_started | in_progress | done)nowPins: Pinned tasks in “Now” panel (max 10)notes: Per-task text noteslogs: Task completion history with timestampshideoutProgress: Hideout station construction statemapPins: User-placed map pins with labelshideoutInventory: Item counts for hideout constructionpinPresets: Saved map pin configurations
src/db/database.ts:4):
Local-First Design Principles
- Zero backend dependencies: All features work entirely in the browser
- Offline-first: Once API data is cached, the app works offline
- Privacy by default: User progress never leaves the device (unless manually exported)
- Instant updates: All mutations are synchronous writes to IndexedDB
- Shareable exports: QR code + JSON export for cross-device syncing
Data Separation Strategy
| Data Type | Storage | TTL | Why |
|---|---|---|---|
| Game data (quests, maps, items) | Memory (TanStack Query) | 12 hours | Upstream data changes frequently; no schema migration needed |
| User data (progress, notes, pins) | IndexedDB (Dexie) | Forever | User-owned, must persist across sessions |
| UI state (filters, view mode) | Zustand (memory) | Session only | Ephemeral, no need to persist |
Why not persist API data?
Storing game data in IndexedDB would require schema migrations on every upstream API change. By keeping it in memory, we get fresh data on refresh and avoid version conflicts.
Storing game data in IndexedDB would require schema migrations on every upstream API change. By keeping it in memory, we get fresh data on refresh and avoid version conflicts.
Key Architectural Decisions
No Backend Server
Why: Tarkov Kappa Navi is a personal progress tracker. A backend would add cost, latency, and privacy concerns with no meaningful benefit.Dynamic Lock Calculation
Why: Lock state depends on current player level and task completion. Caching it would require invalidation logic on every progress change. Computing it on-demand is simpler and always correct.Separate State Management (Zustand + Dexie)
Why: Zustand is optimized for ephemeral UI state with React integration. Dexie is optimized for persistent structured data with IndexedDB. Using both keeps each concern isolated and avoids overloading a single state system.12-Hour Cache TTL
Why: Tarkov quest data rarely changes mid-wipe. 12 hours balances freshness with reduced API load. ThegcTime: Infinity ensures the last successful response is always available as a fallback.