Skip to main content
Vocab Vault follows a clear, scalable directory structure that separates concerns and makes the codebase easy to navigate.

Root Directory

vocab-vault-alpha/
├── src/                   # Application source code
├── public/                # Static assets
├── dist/                  # Production build output
├── node_modules/          # Dependencies
├── android/               # Capacitor Android project
├── ios/                   # Capacitor iOS project (if added)
├── patches/               # patch-package modifications
├── package.json           # Dependencies and scripts
├── vite.config.ts         # Vite build configuration
├── capacitor.config.ts    # Capacitor native config
├── tailwind.config.ts     # Tailwind design system
├── tsconfig.json          # TypeScript configuration
├── eslint.config.js       # ESLint rules
└── README.md              # Project documentation

Source Structure (src/)

The src/ directory contains all application code, organized by responsibility:
src/
├── components/            # React components
│   ├── ui/               # Reusable UI primitives (shadcn)
│   └── *.tsx             # Feature-specific components
├── hooks/                # Custom React hooks
├── lib/                  # Utility functions and helpers
├── data/                 # Static data and configurations
├── pages/                # Route-level components
├── App.tsx               # Root application component
├── main.tsx              # Entry point
├── index.css             # Global styles and Tailwind imports
└── vite-env.d.ts         # Vite type definitions

Components Directory

Feature Components (src/components/)

Top-level components representing major features:
components/
├── AboutModal.tsx          # App information and credits
├── AchievementPopup.tsx    # Achievement celebration overlay
├── AchievementsModal.tsx   # Achievement showcase dialog
├── CategoryCard.tsx        # Category selection card
├── CategoryCarousel.tsx    # Swipeable category carousel
├── CategoryComplete.tsx    # Category completion celebration
├── CategoryMastered.tsx    # Category mastery celebration
├── DailyGoalWidget.tsx     # Daily progress tracker
├── ErrorBoundary.tsx       # Error catching boundary
├── FlashCard.tsx           # Core flashcard with flip/swipe
├── Footer.tsx              # App footer with navigation
├── GoalSettingsModal.tsx   # Daily goal configuration
├── Header.tsx              # App header with logo and actions
├── NavLink.tsx             # Navigation link component
├── QuizMode.tsx            # SRS quiz interface
├── SearchBar.tsx           # Term search functionality
├── StatsModal.tsx          # Progress statistics dialog
├── StudyFooter.tsx         # Study mode footer controls
├── StudyHeader.tsx         # Study mode header
├── ThemeSelector.tsx       # Dark/light theme switcher
└── WelcomeScreen.tsx       # First-time user onboarding
Naming convention:
  • PascalCase for component files
  • Descriptive names ending in component type (Modal, Card, Widget)
  • One component per file

UI Primitives (src/components/ui/)

Reusable, unstyled components from shadcn/ui:
ui/
├── button.tsx              # Button with variants
├── dialog.tsx              # Modal dialog primitive
├── input.tsx               # Text input field
├── label.tsx               # Form label
├── separator.tsx           # Horizontal/vertical divider
├── skeleton.tsx            # Loading placeholder
├── sonner.tsx              # Toast notification provider
├── toast.tsx               # Toast notification component
├── toaster.tsx             # Toast container
├── toggle.tsx              # Toggle button
└── tooltip.tsx             # Hover/focus popover
UI components are copied from shadcn/ui, not installed via npm. This gives you full control to modify them.
Usage pattern:
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";

<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Open</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogTitle>Title</DialogTitle>
    {/* Content */}
  </DialogContent>
</Dialog>

Hooks Directory (src/hooks/)

Custom React hooks encapsulating business logic:
hooks/
├── useProgress.ts          # Main progress and state management
├── useDailyGoals.ts        # Daily goal tracking
├── useSpeech.ts            # Text-to-speech functionality
├── use-mobile.tsx          # Mobile detection hook
└── use-toast.ts            # Toast notification hook

Hook Responsibilities

Central state management hookManages:
  • Term progress (known, learning, unseen)
  • Spaced repetition data (SRS cards)
  • Streak tracking
  • Achievements
  • ELI5 mode toggle
  • Statistics (total cards viewed, logo clicks)
Key functions:
markTerm(termId, status)       // Classic mode
reviewCard(termId, quality)    // SRS mode
getSrsStudyQueue(categoryId)   // Get study queue
updateStreak()                 // Update daily streak
Located at: ~/workspace/source/src/hooks/useProgress.ts:1

Library Directory (src/lib/)

Utility functions and algorithms:
lib/
├── sm2.ts                  # Spaced Repetition System (SM-2 algorithm)
├── utils.ts                # General utilities (cn, classNames)
├── safeJson.ts             # Safe JSON parsing with fallbacks
├── share.ts                # Native share functionality
├── date.ts                 # Date manipulation helpers
├── array.ts                # Array utilities (shuffle, etc.)
└── logger.ts               # Logging utilities

Key Utilities

Implements the SuperMemo 2 (SM-2) algorithm for optimized learning.Core functions:
initializeCard(termId: number): SRSCard
processReview(card: SRSCard, quality: number): SRSCard
getDueCards(cards: Record<number, SRSCard>): SRSCard[]
getStudyQueue(termIds: number[], cards, limit): number[]
getRetentionStats(cards): Stats
getMasteryLevel(card): 'new' | 'learning' | 'reviewing' | 'mastered'
Quality ratings:
  • 0: Complete blackout
  • 1: Wrong, but recognized
  • 2: Wrong, seemed easy
  • 3: Correct with difficulty
  • 4: Correct with hesitation
  • 5: Perfect recall
Location: ~/workspace/source/src/lib/sm2.ts:1
import { cn } from "@/lib/utils";

// Merge Tailwind classes intelligently
cn("px-4 py-2", "px-6")  // Result: "px-6 py-2"
Combines:
  • clsx - Conditional class names
  • tailwind-merge - Deduplicates conflicting classes
import { safeJsonParse } from "@/lib/safeJson";

// Returns fallback instead of throwing
const data = safeJsonParse(localStorage.getItem('key'), []);
Prevents crashes from corrupted localStorage.
import { shareProgress } from "@/lib/share";

// Uses Capacitor Share plugin on mobile, fallback on web
await shareProgress({
  title: "My Vocab Vault Progress",
  text: "I've learned 150 terms!",
});
import { getLocalDateKey, diffDateKeys } from "@/lib/date";

// Get YYYY-MM-DD in user's timezone
const today = getLocalDateKey();  // "2026-03-03"

// Days between date keys
const diff = diffDateKeys("2026-03-03", "2026-03-01");  // 2
Crucial for streak calculations across timezones.

Data Directory (src/data/)

Static data and configuration:
data/
├── vocabulary.ts           # 600+ terms across 24 categories
└── achievements.ts         # Achievement definitions and conditions

Vocabulary Structure

Type definitions:
interface Term {
  id: number;
  term: string;
  definition: string;
  eli5Definition?: string;    // Simplified definition
  category: string;
  example?: string;           // ASCII visual example
}

const categories = [
  {
    id: "foundation",
    name: "Foundation",
    subtitle: "Core Concepts",
    icon: "architecture",
    abbrev: "FDN",
    colorClass: "bg-category-yellow"
  },
  // ... 23 more categories
];
Categories (24 total):
  • Foundation, APIs, Code, Development, Git, Cloud
  • UI/UX, CSS, AI, No-Code, Money, Tools
  • Shortcuts, Security, Debugging, Analytics
  • Mobile, Data, SEO, Testing, Architecture
  • Hosting, AI Tools, Package
Term example:
{
  id: 1,
  term: "Vibe Coding",
  definition: "Building apps by describing what you want to AI, instead of writing code yourself",
  eli5Definition: "Telling a robot what to build and it builds it for you - like magic!",
  category: "foundation"
}
Location: ~/workspace/source/src/data/vocabulary.ts:1

Achievements Structure

interface Achievement {
  id: string;
  title: string;
  description: string;
  icon: string;
  condition: (stats: UserStats) => boolean;
}

const ACHIEVEMENTS = [
  {
    id: "first_card",
    title: "First Step",
    description: "View your first flashcard",
    icon: "👀",
    condition: (stats) => stats.totalCardsViewed >= 1
  },
  // ... more achievements
];

Pages Directory (src/pages/)

Route-level components:
pages/
├── Index.tsx               # Main app page (home/dashboard)
└── NotFound.tsx            # 404 error page
Index.tsx: Contains the entire app experience:
  • Category selection grid
  • Study mode (classic + SRS)
  • Search functionality
  • Modals (stats, achievements, settings)
NotFound.tsx: Catch-all route for invalid URLs.

Configuration Files

vite.config.ts

Build configuration from ~/workspace/source/vite.config.ts:1:
export default defineConfig(({ mode }) => ({
  server: {
    host: "::",
    port: 8080,
  },
  plugins: [
    react(),
    mode === "development" && componentTagger(),
    mode === "production" && visualizer(),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('vocabulary.ts')) return 'vocabulary';
          if (id.includes('framer-motion')) return 'vendor-framer';
          if (id.includes('node_modules')) return 'vendor';
        },
      },
    },
  },
}));
Key settings:
  • @ alias for cleaner imports (@/components/ui/button)
  • Code splitting for vocabulary and animations
  • Development plugins for debugging
  • Production bundle analysis

capacitor.config.ts

Native app configuration from ~/workspace/source/capacitor.config.ts:1:
const config: CapacitorConfig = {
  appId: 'com.dbcreations.vocabvault',
  appName: 'Vocab Vault',
  webDir: 'dist'
};
What this does:
  • appId: Unique identifier for app stores
  • appName: Display name on device
  • webDir: Where Vite builds to (dist/)

tailwind.config.ts

Design system configuration from ~/workspace/source/tailwind.config.ts:1:
export default {
  darkMode: ["class"],
  content: ["./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      fontFamily: {
        sans: ["Inter", "sans-serif"],
        display: ["Space Grotesk", "sans-serif"],
      },
      colors: {
        category: { /* 24 colors */ },
        // ... semantic colors
      },
      borderRadius: {
        xl: "1.5rem",
        "2xl": "2rem",
      },
    },
  },
};

File Naming Conventions

Components

PascalCase.tsxExamples:
  • FlashCard.tsx
  • CategoryCard.tsx
  • AchievementPopup.tsx

Hooks

camelCase.tsExamples:
  • useProgress.ts
  • useDailyGoals.ts
  • use-mobile.tsx

Utilities

camelCase.tsExamples:
  • utils.ts
  • safeJson.ts
  • sm2.ts

Data

camelCase.tsExamples:
  • vocabulary.ts
  • achievements.ts

Import Aliases

The @ alias points to src/:
// Instead of:
import { Button } from "../../../components/ui/button";

// Use:
import { Button } from "@/components/ui/button";
Common patterns:
import { FlashCard } from "@/components/FlashCard";
import { Button } from "@/components/ui/button";
import { useProgress } from "@/hooks/useProgress";
import { cn } from "@/lib/utils";
import { terms } from "@/data/vocabulary";

Build Output (dist/)

Production build structure:
dist/
├── index.html              # Entry HTML
├── assets/
│   ├── index-[hash].js    # Main app bundle
│   ├── vocabulary-[hash].js  # Vocabulary data chunk
│   ├── vendor-[hash].js   # Dependencies
│   ├── vendor-framer-[hash].js  # Framer Motion
│   └── index-[hash].css   # Compiled Tailwind
└── stats.html              # Bundle analyzer (prod only)
Hash-based filenames: Enable aggressive caching - new deploy = new hash = cache bust.

Native Projects

Android (android/)

Generated by Capacitor:
android/
├── app/
│   ├── src/main/
│   │   ├── assets/www/    # Synced from dist/
│   │   ├── res/           # App icons, splash screens
│   │   └── AndroidManifest.xml
│   └── build.gradle
└── build.gradle
Don’t manually edit assets/www/ - it’s overwritten by npx cap sync
Sync process:
npm run build              # Build web assets to dist/
npx cap sync android       # Copy dist/ to android/app/src/main/assets/www/

Adding New Files

New Component

# Create component
touch src/components/NewFeature.tsx
// src/components/NewFeature.tsx
import { useProgress } from "@/hooks/useProgress";
import { Button } from "@/components/ui/button";

export function NewFeature() {
  const { progress } = useProgress();
  
  return (
    <div className="p-4">
      <Button>Action</Button>
    </div>
  );
}

New Hook

touch src/hooks/useNewFeature.ts
// src/hooks/useNewFeature.ts
import { useState, useEffect } from 'react';
import { Preferences } from '@capacitor/preferences';

export function useNewFeature() {
  const [state, setState] = useState<string>('');
  
  useEffect(() => {
    // Load from storage
    const load = async () => {
      const { value } = await Preferences.get({ key: 'newFeature' });
      if (value) setState(value);
    };
    load();
  }, []);
  
  return { state, setState };
}

New Utility

touch src/lib/newUtil.ts
// src/lib/newUtil.ts
export function formatNumber(num: number): string {
  return new Intl.NumberFormat('en-US').format(num);
}

Testing Structure

src/
├── test/
│   ├── setup.ts           # Test environment setup
│   └── utils.tsx          # Test utilities
└── [component].test.tsx   # Tests alongside components
Example test:
// src/components/FlashCard.test.tsx
import { render, screen } from '@testing-library/react';
import { FlashCard } from './FlashCard';

test('renders term', () => {
  render(<FlashCard term={{ id: 1, term: "API", ... }} />);
  expect(screen.getByText('API')).toBeInTheDocument();
});

Environment Variables

Vite uses .env files:
# .env.local (git-ignored)
VITE_API_KEY=abc123
// Access in code
const apiKey = import.meta.env.VITE_API_KEY;
Prefix with VITE_ to expose to client-side code.

Best Practices

1

Keep components small

Break down complex components into smaller, focused pieces.
// Good
<StudyMode>
  <StudyHeader />
  <FlashCard />
  <StudyFooter />
</StudyMode>

// Avoid
<StudyMode> {/* 500 lines */} </StudyMode>
2

Colocate related files

Keep tests, types, and utilities near the components that use them.
3

Use barrel exports sparingly

Avoid index.ts files that re-export everything - they can slow down builds.
4

Organize imports

Group imports by category:
// 1. External dependencies
import { useState } from 'react';
import { motion } from 'framer-motion';

// 2. Internal absolute imports
import { Button } from '@/components/ui/button';
import { useProgress } from '@/hooks/useProgress';

// 3. Relative imports
import { LocalComponent } from './LocalComponent';

This structure scales from 1 developer to a small team while keeping the codebase navigable and maintainable.

Build docs developers (and LLMs) love