Skip to main content
Tarkov Kappa Navi is built with modern web technologies optimized for local-first, offline-capable experiences.

Core Technologies

React 18.3.1

What: UI library with concurrent rendering and automatic batching
Why: Industry-standard component model, excellent TypeScript support, massive ecosystem
Location: All components in src/components/
"react": "^18.3.1",
"react-dom": "^18.3.1"
Key React 18 features used:
  • Concurrent rendering for smooth UI updates during heavy calculations
  • Automatic batching of state updates (reduces re-renders)
  • Strict mode for detecting side effects in development

TypeScript 5.6.2

What: Statically-typed JavaScript superset
Why: Catch bugs at compile time, better IDE autocomplete, self-documenting code
Location: All .ts and .tsx files
"typescript": "~5.6.2"
Configuration (tsconfig.json):
  • Strict mode enabled (strict: true)
  • Module resolution: Bundler
  • Target: ES2020
The codebase maintains 100% TypeScript coverage — no .js files except config. This eliminates entire classes of runtime errors.

Build Tools

Vite 7.3.1

What: Next-generation frontend build tool
Why: Lightning-fast dev server (instant HMR), optimized production builds, zero-config
Location: vite.config.ts
"vite": "^7.3.1",
"@vitejs/plugin-react": "^4.3.3"
Why Vite over Webpack?
  • Dev server starts in less than 1s (vs 10s+ with Webpack)
  • HMR updates in less than 50ms
  • Native ESM in development (no bundling needed)
  • Optimized Rollup-based production builds
Scripts:
"dev": "vite",                    // Dev server on :5173
"build": "tsc -b && vite build",  // Type check + build
"preview": "vite preview"          // Preview production build

State Management

Zustand 5.0.11

What: Lightweight state management library
Why: Simpler than Redux, no Context boilerplate, less than 1KB minified
Location: src/stores/
"zustand": "^5.0.11"
Usage example (tier thresholds store):
import { create } from 'zustand';

interface TierStore {
  thresholds: TierThresholds;
  setThresholds: (t: TierThresholds) => void;
}

export const useTierStore = create<TierStore>((set) => ({
  thresholds: DEFAULT_THRESHOLDS,
  setThresholds: (thresholds) => set({ thresholds }),
}));
Why Zustand instead of Redux?
  • No boilerplate (no actions, reducers, providers)
  • Direct mutation syntax (no immer needed)
  • Built-in TypeScript support
  • Only used for ephemeral UI state, not persistence

TanStack Query 5.90.20

What: Async state management for server data
Why: Built-in caching, retry logic, stale-while-revalidate, request deduplication
Location: src/api/hooks.ts
"@tanstack/react-query": "^5.90.20"
Why TanStack Query (React Query)?
  • Declarative API for async operations (useQuery, useMutation)
  • Automatic background refetching with configurable stale time
  • Built-in error/retry handling (2 retries by default)
  • Global cache with per-query keys (['tasks', lang])
  • Prevents duplicate requests (deduplication)
  • Optimistic updates for mutations
Alternative considered: SWR (similar library by Vercel). TanStack Query was chosen for more granular cache control (gcTime: Infinity).

Database

Dexie 4.3.0

What: Minimalistic wrapper for IndexedDB
Why: Promise-based API, typed queries, transaction handling, compound indexes
Location: src/db/database.ts
"dexie": "^4.3.0"
Schema definition:
export class TarkovKappaNaviDB extends Dexie {
  profile!: Table<Profile, string>;
  progress!: Table<ProgressRow, string>;
  nowPins!: Table<NowPins, string>;
  // ...

  constructor() {
    super('TarkovKappaNaviDB');
    this.version(1).stores({
      profile: 'id',
      progress: 'taskId, status',
      mapPins: 'id, [mapId+wipeId+viewMode]', // Compound index
    });
  }
}
Why Dexie over raw IndexedDB?
  • IndexedDB API is callback-based and verbose
  • Dexie provides async/await syntax
  • Automatic transaction handling
  • Built-in schema versioning for migrations
  • TypeScript-first design (Table<T, K>)
Alternative considered: LocalForage (simpler but lacks advanced querying). Dexie’s compound indexes were critical for performant map pin queries.

Routing

React Router 7.13.0

What: Declarative routing for React
Why: Standard routing solution, code-splitting support, nested routes
Location: src/App.tsx:1
"react-router-dom": "^7.13.0"
Route structure:
<BrowserRouter>
  <Routes>
    <Route path="/share" element={<ShareImportPage />} />
    <Route element={<AppShell />}>  {/* Layout wrapper */}
      <Route path="/dashboard" element={<DashboardPage />} />
      <Route path="/tasks" element={<TasksPage />} />
      <Route path="/map" element={<MapPage />} />
      {/* ... */}
    </Route>
  </Routes>
</BrowserRouter>
Why React Router v7?
  • Latest version with improved TypeScript types
  • Built-in code-splitting with lazy loading
  • Support for nested layouts (AppShell wrapper)
  • URL-based state for share links (/share?data=...)

Validation

Zod 4.3.6

What: TypeScript-first schema validation
Why: Runtime validation for user imports, API responses, QR code data
Location: Used in import/export flows
"zod": "^4.3.6"
Example usage (validating exported data):
import { z } from 'zod';

const ExportSchema = z.object({
  profile: z.object({ level: z.number(), wipeId: z.string() }),
  progress: z.array(z.object({ taskId: z.string(), status: z.enum(['not_started', 'in_progress', 'done']) })),
  // ...
});

export function validateImport(data: unknown) {
  return ExportSchema.parse(data); // Throws on invalid data
}
Why Zod?
  • Schema definition doubles as TypeScript type (z.infer<typeof ExportSchema>)
  • Excellent error messages for validation failures
  • Composable schemas (can extend/merge)
  • Critical for data integrity when importing user-submitted JSON/QR codes

Styling

CSS Modules

What: Scoped CSS with local class names
Why: No global namespace pollution, tree-shakeable, co-located with components
Location: *.module.css files alongside components
Example:
/* TaskCard.module.css */
.card {
  border: 1px solid #ccc;
  padding: 1rem;
}

.title {
  font-size: 1.2rem;
  font-weight: bold;
}
// TaskCard.tsx
import styles from './TaskCard.module.css';

export function TaskCard() {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>Task Name</h3>
    </div>
  );
}
Why CSS Modules over alternatives?
  • vs Tailwind: More semantic class names, easier to theme
  • vs CSS-in-JS (styled-components): Zero runtime cost, better performance
  • vs global CSS: Avoids class name collisions, co-located with components

Testing

Vitest 4.0.18

What: Vite-native unit test framework
Why: Instant test runs, same config as Vite, Jest-compatible API
Location: src/domain/__tests__/
"vitest": "^4.0.18"
Example test (src/domain/__tests__/unlock.test.ts):
import { describe, it, expect } from 'vitest';
import { isTaskUnlocked } from '../unlock';

describe('isTaskUnlocked', () => {
  it('returns false if player level is too low', () => {
    const task = { minPlayerLevel: 10, prereqIds: [] };
    const progressMap = new Map();
    expect(isTaskUnlocked(task, 5, progressMap)).toBe(false);
  });
});
Scripts:
"test": "vitest run",     // Run once
"test:watch": "vitest"    // Watch mode
Why Vitest over Jest?
  • Native Vite integration (no babel config needed)
  • ~10x faster test runs (uses same transform pipeline as dev server)
  • ESM support out of the box
  • Jest-compatible API (easy migration path)

Utilities

pako 2.1.0

What: Zlib compression library
Why: Compress exported data for smaller QR codes
Location: QR export flow (src/lib/share.ts)
"pako": "^2.1.0",
"@types/pako": "^2.0.4"
Usage:
import pako from 'pako';

const json = JSON.stringify(exportData);
const compressed = pako.deflate(json); // Uint8Array
const base64 = btoa(String.fromCharCode(...compressed));
Why compression?
  • Uncompressed export JSON: ~50KB for full progress
  • Compressed: ~5KB (90% reduction)
  • QR codes have size limits; compression makes data shareable

qrcode.react 4.2.0

What: QR code generator for React
Why: Generate scannable QR codes for data sharing
Location: Share export page
"qrcode.react": "^4.2.0"
Usage:
import { QRCodeSVG } from 'qrcode.react';

<QRCodeSVG value={compressedData} size={256} />

react-helmet-async 2.0.5

What: Manage document head (title, meta tags)
Why: Set page titles dynamically (“Dashboard | Tarkov Kappa Navi”)
Location: src/App.tsx:3
"react-helmet-async": "^2.0.5"
Usage:
import { Helmet } from 'react-helmet-async';

export function DashboardPage() {
  return (
    <>
      <Helmet>
        <title>Dashboard | Tarkov Kappa Navi</title>
      </Helmet>
      {/* ... */}
    </>
  );
}

Lucide React 0.575.0

What: Icon library (open-source Feather fork)
Why: Lightweight SVG icons, tree-shakeable, TypeScript types
Location: Used throughout components
"lucide-react": "^0.575.0"
Usage:
import { Check, X, Lock } from 'lucide-react';

<Check size={16} color="green" />
Why Lucide over alternatives?
  • vs react-icons: Smaller bundle (only imports used icons)
  • vs Font Awesome: No font loading, better performance
  • vs heroicons: More icons available (1000+ vs 200)

Linting

ESLint 9.13.0

What: JavaScript/TypeScript linter
Why: Enforce code style, catch bugs before runtime
Location: eslint.config.js
"eslint": "^9.13.0",
"@eslint/js": "^9.13.0",
"typescript-eslint": "^8.11.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14"
Key plugins:
  • typescript-eslint: TypeScript-specific rules
  • eslint-plugin-react-hooks: Enforce Hooks rules (prevent bugs)
  • eslint-plugin-react-refresh: Ensure HMR compatibility
Script:
"lint": "eslint ."

Summary Table

CategoryTechnologyVersionPurpose
UIReact18.3.1Component library
LanguageTypeScript5.6.2Type safety
BuildVite7.3.1Dev server + bundler
State (UI)Zustand5.0.11Ephemeral UI state
State (Server)TanStack Query5.90.20API caching
DatabaseDexie4.3.0IndexedDB wrapper
RoutingReact Router7.13.0Client-side routing
ValidationZod4.3.6Schema validation
StylingCSS Modules(native)Scoped CSS
TestingVitest4.0.18Unit tests
IconsLucide React0.575.0SVG icons
Compressionpako2.1.0Zlib compression
QR Codesqrcode.react4.2.0QR generation
Meta Tagsreact-helmet-async2.0.5Document head
LintingESLint9.13.0Code quality

Design Philosophy

Minimal Dependencies

The project uses only 13 runtime dependencies (vs 50+ in typical React apps). Each dependency must justify its inclusion:
  • Solves a complex problem (IndexedDB → Dexie)
  • Provides significant DX improvement (TanStack Query)
  • No good native/lightweight alternative (QR codes, compression)

Modern, Stable Versions

All dependencies use:
  • Latest stable major versions (no legacy cruft)
  • Active maintenance (no abandoned packages)
  • TypeScript-first design (no @types/* mismatches)

Performance-First Choices

  • Vite over Webpack (10x faster builds)
  • Zustand over Redux (99% smaller bundle)
  • CSS Modules over CSS-in-JS (zero runtime)
  • Vitest over Jest (instant test runs)

Local-First Optimized

  • Dexie for robust offline storage
  • TanStack Query for stale-while-revalidate caching
  • pako for efficient data compression
  • React Router for URL-based state (shareable links)

Build docs developers (and LLMs) love