Skip to content

ajayvers/flashcards

Repository files navigation

Flashcards

A modern flashcard application that helps you learn and retain information using spaced repetition. Built with React, TypeScript, and deployed on Vercel with Neon Postgres and Auth0 authentication.

Table of Contents

About

Flashcards is a modern web application that helps you learn and retain information more effectively using spaced repetition. Whether you're studying a new language, preparing for an exam, or learning technical concepts, this application optimizes your review schedule to maximize long-term retention while minimizing study time.

What is Spaced Repetition?

Spaced repetition is a proven learning technique that schedules reviews of material at increasing intervals. Instead of cramming all at once, you review information just before you're about to forget it. This application uses the SM-2 (SuperMemo 2) algorithm to automatically calculate optimal review times based on how well you know each card. Cards you find difficult appear more frequently, while cards you know well are shown less often, helping you focus your effort where it's needed most.

Key Features

  • 📚 Deck Management: Organize your flashcards into separate decks for different subjects or topics
  • 📝 Smart Review System: Automatic scheduling using the SM-2 algorithm for optimal learning
  • 🔄 Import/Export: Compatible with CSV, JSON, and Anki-style TSV formats for easy data portability
  • ⌨️ Keyboard Shortcuts: Navigate reviews efficiently with keyboard controls
  • 🔐 Secure Authentication: User accounts powered by Auth0 to keep your data private

Open Source & Contributions Welcome

This project is open source and welcomes contributions from developers of all skill levels. Whether you're fixing a bug, adding a feature, improving documentation, or just learning, your contributions are valued. Check out the Contributing section to get started!

Features

[Content to be added]

Deck Management

Organize your flashcards into separate decks for different subjects, topics, or learning goals. Decks help you keep your study materials structured and focused, making it easier to review specific content when you need it.

Creating Decks

Create a new deck by providing a name that describes its content. Each deck is private to your account and can contain unlimited flashcards. When you create a deck, it starts empty and ready for you to add cards.

Use cases:

  • Separate decks for different subjects (e.g., "Spanish Vocabulary", "Biology Terms", "JavaScript Concepts")
  • Topic-specific collections within a subject (e.g., "React Hooks", "Array Methods")
  • Difficulty-based organization (e.g., "Beginner Words", "Advanced Grammar")

Renaming Decks

Rename any deck at any time to better reflect its content as your learning materials evolve. Renaming a deck doesn't affect the cards inside or their review schedules—only the deck's display name changes.

Why rename?

  • Refine deck names as you better understand the content
  • Update names to reflect expanded or narrowed scope
  • Maintain clear organization as your collection grows

Deleting Decks

Remove decks you no longer need. When you delete a deck, all cards within that deck are permanently removed along with their review history. This action cannot be undone, so use it carefully.

When to delete:

  • You've finished learning the material and no longer need reviews
  • You want to start fresh with a new approach to the same topic
  • You're consolidating multiple decks and removing duplicates

Tip: Each deck tracks useful statistics including total card count, cards due for review, and last review date, helping you prioritize which decks need attention.

Card Management

Create, edit, and manage individual flashcards within your decks. Each card represents a single piece of information you want to learn, with a question or prompt on the front and the answer or explanation on the back.

Creating Cards

Add new flashcards to any deck by providing front and back content. The front typically contains a question, term, or prompt, while the back contains the answer, definition, or explanation. Each card also supports optional tags for categorization and notes for additional context.

Card structure:

  • Front (required): The question, term, or prompt shown during review
  • Back (required): The answer, definition, or explanation revealed after you respond
  • Tags (optional): Labels for organizing and filtering cards (e.g., "verb", "chapter-3", "difficult")
  • Notes (optional): Additional context, mnemonics, or explanations to help you learn

Use cases:

  • Language learning: Front = "hello", Back = "hola", Tags = ["greeting", "basic"]
  • Technical concepts: Front = "What is closure?", Back = "A function that captures variables from its outer scope", Tags = ["javascript", "functions"]
  • Historical facts: Front = "When did World War II end?", Back = "1945", Notes = "VE Day: May 8, VJ Day: September 2"

Editing Cards

Update any card's content at any time to fix typos, improve clarity, or add additional information. You can modify the front text, back text, tags, or notes independently. Editing a card doesn't affect its review schedule—your learning progress is preserved.

Why edit?

  • Fix spelling or grammar mistakes
  • Clarify ambiguous questions or answers
  • Add tags as you develop better organizational systems
  • Include notes with memory aids or additional context
  • Refine cards based on what you find confusing during reviews

Tip: If you find yourself consistently struggling with a card during reviews, editing it to be clearer or adding helpful notes can make it easier to remember.

Deleting Cards

Remove cards you no longer need from your deck. When you delete a card, it's permanently removed along with its review history and progress data. This action cannot be undone.

When to delete:

  • You've mastered the material and no longer need to review it
  • The card contains incorrect or outdated information
  • You're consolidating duplicate cards
  • The card doesn't fit your learning goals anymore

Note: Deleting a card removes it from all future review sessions immediately. If you're unsure, consider keeping the card—the spaced repetition algorithm will automatically show well-known cards very infrequently.

Smart Review System

The application uses an intelligent spaced repetition algorithm to optimize your learning. Instead of reviewing all cards equally, the system adapts to your performance, showing you each card at the optimal moment for maximum retention with minimum effort.

How It Works: The SM-2 Algorithm

The review system is powered by the SM-2 (SuperMemo 2) algorithm, a proven spaced repetition method developed in the 1980s and still widely used today. Here's how it works in simple terms:

The Core Concept: When you review a card, you rate how well you knew it. Based on your rating, the algorithm calculates when you should see that card again. Cards you know well are scheduled further into the future, while cards you struggle with appear more frequently.

Your Rating Options:

  • Again (0): You didn't remember the card at all
  • Hard (1): You remembered with significant difficulty
  • Good (2): You remembered with some effort
  • Easy (3): You remembered easily and quickly

How Intervals Are Calculated:

  1. First Review: When you see a new card for the first time and rate it "Hard" or better, you'll see it again in 1 day.

  2. Second Review: If you remember it correctly the second time, the interval extends to 6 days.

  3. Subsequent Reviews: After that, each successful review multiplies the interval by an "ease factor" (typically starting at 2.5). For example:

    • After 6 days → Next review in ~15 days (6 × 2.5)
    • After 15 days → Next review in ~38 days (15 × 2.5)
    • After 38 days → Next review in ~95 days (38 × 2.5)
  4. Difficulty Adjustments: The ease factor adjusts based on your performance:

    • Rate a card "Easy" → The ease factor increases slightly, making future intervals longer
    • Rate a card "Hard" → The ease factor decreases slightly, making future intervals shorter
    • Rate a card "Again" → The card resets to a 1-day interval, and you start the learning process over

Example Learning Journey:

Imagine you're learning the Spanish word "biblioteca" (library):

  • Day 1: You see the card for the first time and rate it "Good" → Next review in 1 day
  • Day 2: You remember it and rate it "Good" → Next review in 6 days
  • Day 8: You remember it easily and rate it "Easy" → Next review in ~16 days (ease factor increased)
  • Day 24: You struggle a bit and rate it "Hard" → Next review in ~30 days (ease factor decreased)
  • Day 54: You remember it well and rate it "Good" → Next review in ~75 days

Over time, cards you know well fade into the background, appearing only occasionally to maintain retention, while difficult cards stay in active rotation until you master them.

Benefits for Long-Term Retention

The SM-2 algorithm is designed around how human memory actually works:

  • Optimal Timing: Reviews happen just before you're about to forget, which strengthens memory more effectively than reviewing too early or too late.

  • Efficient Learning: You spend more time on difficult material and less time on what you already know, making study sessions more productive.

  • Long-Term Retention: By gradually increasing intervals, the algorithm helps move information from short-term to long-term memory. Cards you've mastered can have intervals of months or even years while still maintaining retention.

  • Reduced Study Time: Instead of reviewing everything repeatedly, you focus your effort where it's needed most. Research shows spaced repetition can reduce study time by 50% or more compared to traditional methods.

  • Personalized Learning: The algorithm adapts to your individual performance. Two people studying the same deck will have completely different review schedules based on what each person finds easy or difficult.

In Practice: When you start a review session, the application shows you all cards that are due for review today. As you rate each card, the algorithm immediately calculates the next review date. Over time, you'll notice that your daily review queue becomes more manageable as cards you've mastered move to longer intervals, while new or difficult cards remain in active rotation.

Import/Export

Move your flashcard data in and out of the application with support for multiple file formats. Whether you're migrating from another flashcard app, backing up your decks, or sharing study materials with others, the import/export feature makes it easy to work with your data.

Supported Formats

The application supports three popular formats, each designed for different use cases:

CSV (Comma-Separated Values)

CSV is a spreadsheet-friendly format that works with Excel, Google Sheets, and other spreadsheet applications. This format is ideal when you want to create or edit flashcards in bulk using familiar spreadsheet tools.

Format structure:

front,back,tags,notes
"What is the capital of France?","Paris","geography,europe","Remember: City of Light"
"Define photosynthesis","Process by which plants convert light into energy","biology,plants",""

Features:

  • Columns: front (required), back (required), tags (optional), notes (optional)
  • Tags: Multiple tags separated by commas within the tags column
  • Editing: Open in any spreadsheet application for easy bulk editing
  • Compatibility: Works with Excel, Google Sheets, LibreOffice Calc, and other spreadsheet tools

Use cases:

  • Creating large decks from scratch using spreadsheet formulas and data manipulation
  • Editing multiple cards at once with find-and-replace or bulk operations
  • Collaborating with others who prefer spreadsheet tools
  • Importing data from other sources that export to CSV

JSON (JavaScript Object Notation)

JSON is a structured data format that preserves deck metadata and provides a complete representation of your deck. This format is ideal for backups, data migration, and programmatic access.

Format structure:

{
  "version": 1,
  "deck": {
    "name": "Spanish Vocabulary"
  },
  "cards": [
    {
      "front": "hello",
      "back": "hola",
      "tags": ["greeting", "basic"],
      "notes": "Common greeting"
    },
    {
      "front": "goodbye",
      "back": "adiós",
      "tags": ["greeting", "basic"],
      "notes": null
    }
  ]
}

Features:

  • Deck metadata: Includes deck name in the export
  • Structured data: Clear hierarchy with proper data types
  • Tags as arrays: Tags are represented as proper arrays, not comma-separated strings
  • Null handling: Distinguishes between empty strings and null values
  • Version tracking: Includes format version for future compatibility

Use cases:

  • Creating complete backups of your decks with all metadata
  • Migrating decks between different instances of the application
  • Programmatically generating flashcards from other data sources
  • Integrating with other tools that consume JSON data
  • Version control: JSON files work well with Git for tracking changes over time

TSV (Tab-Separated Values) - Anki-Style

TSV is a simple, minimal format compatible with Anki, one of the most popular flashcard applications. This format uses tab characters to separate the front and back of each card, with one card per line.

Format structure:

What is the capital of France?	Paris
Define photosynthesis	Process by which plants convert light into energy
What year did World War II end?	1945

Features:

  • Minimal structure: Just front and back, separated by a tab character
  • No headers: File contains only card data, no column headers
  • Anki compatibility: Direct import/export with Anki desktop and mobile apps
  • Simplicity: Easy to create manually or with simple scripts

Limitations:

  • No tags or notes: Only front and back content is supported
  • No metadata: Deck name and other metadata are not included
  • Basic only: Best for simple cards without additional context

Use cases:

  • Importing decks from Anki or exporting to Anki
  • Sharing simple flashcard sets with Anki users
  • Creating quick, minimal flashcard sets without extra metadata
  • Migrating between flashcard applications that support TSV format

Choosing the Right Format

Use CSV when:

  • You want to create or edit cards in a spreadsheet application
  • You need to include tags and notes with your cards
  • You're comfortable with spreadsheet tools and want bulk editing capabilities

Use JSON when:

  • You want a complete backup with deck metadata
  • You're migrating data between systems
  • You need programmatic access to your flashcard data
  • You want to track changes in version control

Use TSV when:

  • You're importing from or exporting to Anki
  • You only need basic front/back cards without tags or notes
  • You want the simplest possible format
  • You're sharing with Anki users

Tip: All formats preserve the front and back content of your cards. If you need tags and notes, use CSV or JSON. If you're working with Anki, use TSV for maximum compatibility.

Keyboard Shortcuts

Navigate review sessions efficiently with keyboard shortcuts. Instead of clicking buttons with your mouse, you can use your keyboard to reveal answers, grade cards, and move between cards—making review sessions faster and more focused.

Available Shortcuts

The application provides keyboard shortcuts for all common review actions:

During Review (Front of Card Showing):

  • Space or Enter - Reveal the answer on the back of the card
  • Left Arrow (←) - Go to the previous card (if available)
  • Right Arrow (→) - Go to the next card (if available)

During Review (Back of Card Showing):

  • 1 - Grade the card as "Again" (didn't remember)
  • 2 - Grade the card as "Hard" (remembered with difficulty)
  • 3 - Grade the card as "Good" (remembered with some effort)
  • 4 - Grade the card as "Easy" (remembered easily)
  • Left Arrow (←) - Go to the previous card (if available)
  • Right Arrow (→) - Go to the next card (if available)

When Review Session is Complete:

  • Escape or Enter - Return to the card list

Smart Behavior

The keyboard shortcuts are designed to be intuitive and safe:

  • Disabled during typing: Shortcuts are automatically disabled when you're typing in input fields, textareas, or any editable content. This prevents accidental actions while you're entering data elsewhere in the application.

  • Disabled during save: Shortcuts are temporarily disabled while a grade is being saved to the server. This prevents you from accidentally grading the same card multiple times or skipping cards while the previous action is still processing.

  • Context-aware: The available shortcuts change based on what you're doing. For example, grading shortcuts (1-4) only work when the answer is revealed, and navigation shortcuts are disabled when you've reached the first or last card.

Benefits for Efficient Reviews

Using keyboard shortcuts significantly improves your review experience:

  • Faster reviews: Keep your hands on the keyboard instead of switching between keyboard and mouse. This reduces friction and helps you maintain focus during study sessions.

  • Better flow: Keyboard shortcuts create a smooth, uninterrupted rhythm during reviews. Reveal, grade, next card—all without moving your hands from the home row.

  • Reduced cognitive load: Once you learn the shortcuts, they become muscle memory. You can focus entirely on the content you're learning rather than thinking about how to interact with the interface.

  • Accessibility: Keyboard navigation makes the application more accessible for users who prefer or require keyboard-only interaction.

Tip: The number keys (1-4) for grading are positioned in order of difficulty, making them easy to remember: 1 is the hardest (Again), and 4 is the easiest (Easy). After a few review sessions, using these shortcuts will become second nature.

Tech Stack

[Content to be added]

Frontend

  • React 19 - A JavaScript library for building user interfaces with a component-based architecture. React makes it easy to create interactive UIs by breaking them down into reusable components. We chose React for its excellent TypeScript support, large ecosystem of tools and libraries, and efficient rendering that keeps the application fast and responsive.

  • TypeScript - A typed superset of JavaScript that adds static type checking to your code. TypeScript catches errors before your code even runs, making development faster and safer. It also provides better IDE support with autocomplete and inline documentation. We use TypeScript throughout the project to maintain code quality and make the codebase easier to understand and maintain.

  • Vite - A next-generation build tool that provides an extremely fast development server and optimized production builds. Unlike older tools, Vite uses native ES modules in the browser during development, which means instant server startup and lightning-fast hot module replacement (your changes appear in the browser almost instantly). We chose Vite for its speed and modern approach to frontend tooling.

  • Tailwind CSS 4 - A utility-first CSS framework that lets you build custom designs rapidly without writing custom CSS. Instead of creating CSS classes, you apply small utility classes directly in your HTML/JSX (like flex, text-center, bg-blue-500). This approach leads to consistent styling, smaller CSS bundles, and faster development. Tailwind was chosen for its excellent developer experience and ability to create responsive, accessible designs quickly.

Backend

  • Vercel Serverless Functions - Serverless API endpoints that run on-demand without managing servers. Each API route in the api/ directory becomes an independent function that automatically scales based on traffic. Vercel handles all the infrastructure, so you can focus on writing code. We chose serverless functions for their automatic scaling, zero server management, and seamless integration with the frontend deployment—everything deploys together with a single command.

  • Node.js - The JavaScript runtime that powers the serverless functions. Node.js enables full-stack JavaScript development, meaning you can use the same language and share code between frontend and backend. Its excellent async I/O performance makes it ideal for handling API requests efficiently.

  • Formidable - A Node.js module for parsing form data, especially file uploads. This library handles the multipart form data when users import flashcard decks from CSV or JSON files, making file upload processing straightforward and reliable.

  • Jose - A JavaScript module for working with JSON Web Tokens (JWT) and JSON Web Encryption (JWE). Jose validates the JWT tokens issued by Auth0 in our serverless API functions, ensuring that only authenticated users can access protected endpoints. It's a modern, secure library that follows current web standards.

  • Zod - A TypeScript-first schema validation library that validates API request and response data. Zod ensures that incoming data matches expected formats and types, with automatic TypeScript type inference. This catches data errors early and provides clear error messages, making the API more robust and easier to debug.

  • csv-parse - A CSV parsing library for Node.js that converts CSV files into JavaScript objects. This powers the flashcard import functionality, allowing users to import decks from spreadsheet applications or other flashcard tools that export to CSV format.

Database

  • Neon Postgres - A serverless Postgres database with automatic scaling and modern developer features like database branching. Neon provides a fully managed Postgres database that scales automatically based on usage, perfect for serverless deployments on Vercel. It offers the reliability and SQL features of Postgres without the operational overhead of managing database servers. We chose Neon for its serverless architecture, excellent performance, and seamless integration with modern deployment workflows.

  • Drizzle ORM - A TypeScript-first ORM (Object-Relational Mapping) toolkit with SQL-like syntax and type-safe queries. Drizzle lets you write database queries in TypeScript with full type safety, meaning TypeScript can catch database-related errors before your code even runs. Unlike heavier ORMs, Drizzle stays close to SQL while providing excellent developer experience. This makes development faster and safer by catching mistakes at compile time rather than runtime.

  • Drizzle Kit - The CLI companion tool for Drizzle ORM that manages database schema migrations and provides a database studio for development. Drizzle Kit generates migration files when you change your schema, applies migrations to your database, and includes a web-based database browser for inspecting data during development. This streamlines database workflow and keeps schema changes tracked in version control.

Authentication

  • Auth0 - An authentication and authorization platform that provides secure user login with support for OAuth 2.0 and OpenID Connect. Auth0 handles all the complex security aspects of authentication—password hashing, token management, session handling, and more—so you don't have to build it yourself. It includes features like social login (Google, GitHub, etc.), multi-factor authentication, and user management out of the box. We chose Auth0 for its production-ready security, minimal setup requirements, and comprehensive feature set that would take months to build from scratch.

Deployment

  • Vercel - A cloud platform for deploying static sites and serverless functions with zero configuration. Vercel automatically detects your framework (Vite in this case), builds your project, and deploys both the static frontend and serverless API functions with a single command. It provides automatic HTTPS, a global CDN for fast content delivery, preview deployments for every pull request, and seamless integration with Git repositories. We chose Vercel for its exceptional developer experience—deployments happen in seconds, and the platform handles all infrastructure concerns like scaling, SSL certificates, and edge caching automatically. The static frontend (built from src/) is served from the CDN, while API routes (in api/) become serverless functions that scale automatically based on traffic.

Testing

  • Vitest - A Vite-native unit testing framework with a Jest-compatible API. Vitest is built specifically to work with Vite, which means it shares the same configuration and transformation pipeline as your development environment. This results in extremely fast test execution—tests start instantly and run in parallel. It includes features like watch mode (automatically re-runs tests when files change), code coverage reporting, and snapshot testing. We chose Vitest for its speed, seamless Vite integration, and excellent TypeScript support. Unlike older testing frameworks that require complex configuration, Vitest works out of the box with our existing Vite setup.

  • React Testing Library - A testing utility library focused on testing React components from the user's perspective. Instead of testing implementation details (like component state or methods), React Testing Library encourages you to test how users interact with your components—clicking buttons, typing in inputs, reading text on the screen. This approach leads to more maintainable tests that don't break when you refactor internal component logic. The library provides utilities for rendering components, querying elements (by role, label, or text), and simulating user interactions. We chose React Testing Library because it promotes testing best practices and helps ensure our components work correctly for actual users.

  • @testing-library/jest-dom - A companion library that provides custom matchers for asserting on DOM nodes. It extends the standard assertion library with intuitive matchers like toBeInTheDocument(), toHaveClass(), toBeDisabled(), and toHaveAccessibleName(). These matchers make tests more readable and provide better error messages when assertions fail. For example, instead of writing expect(element.disabled).toBe(true), you can write expect(element).toBeDisabled(), which is clearer and provides more helpful output when the test fails.

  • @testing-library/user-event - A library that simulates realistic user interactions for testing. While React Testing Library provides basic event simulation, user-event goes further by mimicking how real users interact with the browser. For example, when simulating typing, it fires all the appropriate events in the correct order (keydown, keypress, input, keyup), handles focus changes, and respects browser behavior. This makes tests more reliable because they behave like real user interactions. We use user-event to test complex interactions like keyboard shortcuts in the review session.

  • jsdom - A JavaScript implementation of web standards that provides a DOM environment for Node.js. Since tests run in Node.js (not a real browser), jsdom creates a simulated browser environment with a document object, window object, and all the standard DOM APIs. This allows React components to render and interact with the DOM during tests without needing a real browser. Vitest uses jsdom automatically when testing components, making the test environment feel just like a browser while keeping tests fast.

  • pnpm - A fast, disk space-efficient package manager that serves as an alternative to npm and yarn. Unlike npm which duplicates packages for every project, pnpm uses a content-addressable storage system where packages are stored once globally and linked into projects. This means faster installations (packages are often already cached), less disk space usage (no duplicate packages), and stricter dependency management (prevents accidental access to undeclared dependencies). We chose pnpm for its speed and efficiency—installations are typically 2-3x faster than npm, and it uses significantly less disk space in monorepos or when working on multiple projects.

Getting Started

This section will guide you through setting up the flashcard application for local development. You'll need to configure Auth0 for authentication and Neon Postgres for the database, then install dependencies and run the development server.

Prerequisites

Before you begin, make sure you have:

  • Node.js (version 18 or higher) and pnpm installed on your machine
  • An Auth0 account (free tier works fine) for authentication setup
  • A Neon Postgres database (free tier available) for data storage
  • A code editor (VS Code, WebStorm, or your preferred editor)

Local Setup

Follow these steps in order to set up your local development environment:

1. Configure Auth0 Authentication

Auth0 handles user authentication for the application. You'll need to create two resources in your Auth0 dashboard:

Create a Custom API:

  1. Go to your Auth0 Dashboard → ApplicationsAPIsCreate API
  2. Set an Identifier for your API (for example: https://flashcards-api)
    • Important: Do NOT use the default Management API identifier (…/api/v2/)
    • This identifier will be used as the audience for both the frontend and backend
    • The identifier can be any URI format string—it doesn't need to be a real URL
  3. Save the API and note the Identifier for later use

Create a Single Page Application:

  1. Go to your Auth0 Dashboard → ApplicationsApplicationsCreate Application
  2. Choose Single Page Application as the application type
  3. Once created, go to the Settings tab and configure:
    • Allowed Callback URLs: http://localhost:3000 (add your deployed URLs later)
    • Allowed Logout URLs: http://localhost:3000 (add your deployed URLs later)
    • Allowed Web Origins: http://localhost:3000 (add your deployed URLs later)
  4. Note your Domain and Client ID from the Settings tab
  5. Go to the Connections tab and ensure:
    • Username-Password-Authentication is enabled
    • Google social connection is enabled (optional, but recommended)

2. Set Up Environment Variables

  1. Copy the .env.example file to create a new .env file:

    cp .env.example .env
  2. Fill in the values from Auth0 and Neon:

    Auth0 Configuration:

    • AUTH0_ISSUER_BASE_URL: Your Auth0 tenant URL (e.g., https://your-tenant.us.auth0.com/)
      • Important: Include the trailing slash
    • AUTH0_AUDIENCE: The Custom API Identifier from step 1 (e.g., https://flashcards-api)
    • VITE_AUTH0_DOMAIN: Your Auth0 domain without https:// (e.g., your-tenant.us.auth0.com)
    • VITE_AUTH0_CLIENT_ID: Your Single Page Application's Client ID
    • VITE_AUTH0_AUDIENCE: Same as AUTH0_AUDIENCE (e.g., https://flashcards-api)
    • VITE_AUTH0_REDIRECT_URI: Your callback URL (e.g., http://localhost:3000)
    • VITE_AUTH0_USE_REFRESH_TOKENS: Set to true only if you enable Refresh Token Rotation in your Auth0 SPA settings; otherwise leave it unset

    Neon Database Configuration:

    • DATABASE_URL: Your Neon Postgres connection string (get this from your Neon console)

3. Install Dependencies

Install all required packages using pnpm:

pnpm install

4. Run Database Migrations

Set up the database schema by running migrations:

pnpm db:migrate

This creates all necessary tables (decks, cards, review history) in your Neon database.

Running the Application

Start the full-stack development server (both frontend and API):

pnpm dev:full

This command uses Vercel's development server, which runs:

  • The Vite frontend at http://localhost:3000
  • The serverless API functions at http://localhost:3000/api/*

Open your browser and navigate to http://localhost:3000. You should see the login page. Sign up or log in with your Auth0 credentials to start using the application.

Note: If you only run pnpm dev (Vite only), the API endpoints won't be available. Always use pnpm dev:full for local development to ensure both frontend and backend are running.

Deployment

This section guides you through deploying the flashcard application to Vercel, a cloud platform that automatically handles both the static frontend and serverless API functions with zero configuration.

Deploy to Vercel

Vercel provides seamless deployment for full-stack applications like this one. The platform automatically detects the Vite frontend, builds the project, and deploys both the static site and serverless API functions together.

Deployment Steps

  1. Push your repository to Git

    Ensure your code is pushed to a Git repository (GitHub, GitLab, or Bitbucket). Vercel imports directly from your Git provider.

  2. Import the repository in Vercel

    • Go to vercel.com and sign in (or create an account)
    • Click "Add New Project" or "Import Project"
    • Select your Git provider and authorize Vercel to access your repositories
    • Find and select this flashcard application repository
    • Click "Import"
  3. Configure environment variables

    Before deploying, add all required environment variables from your .env.example file:

    • In the Vercel import screen, scroll to "Environment Variables"
    • Add each variable from .env.example with values from your Auth0 and Neon accounts:

    Auth0 Configuration (Frontend):

    • VITE_AUTH0_DOMAIN: Your Auth0 domain (e.g., your-tenant.us.auth0.com)
    • VITE_AUTH0_CLIENT_ID: Your Single Page Application's Client ID
    • VITE_AUTH0_AUDIENCE: Your Custom API Identifier (e.g., https://flashcards-api)
    • VITE_AUTH0_REDIRECT_URI: Your deployed URL (e.g., https://your-app.vercel.app)
    • VITE_AUTH0_USE_REFRESH_TOKENS: Set to true only if you enabled Refresh Token Rotation in Auth0

    Auth0 Configuration (Backend API):

    • AUTH0_ISSUER_BASE_URL: Your Auth0 tenant URL with trailing slash (e.g., https://your-tenant.us.auth0.com/)
    • AUTH0_AUDIENCE: Same as VITE_AUTH0_AUDIENCE (e.g., https://flashcards-api)

    Database Configuration:

    • DATABASE_URL: Your Neon Postgres connection string

    Important: The VITE_* variables are used during the frontend build, while AUTH0_ISSUER_BASE_URL and AUTH0_AUDIENCE (without VITE_) are used by the serverless API functions at runtime. Both sets are required.

  4. Configure build settings

    Vercel usually auto-detects the correct settings, but verify:

    • Build Command: pnpm build
    • Output Directory: dist
    • Install Command: pnpm install (auto-detected)

    These settings tell Vercel how to build your application. The build command compiles the frontend, and the output directory specifies where the built files are located.

  5. Deploy

    Click "Deploy" and wait for the build to complete. Vercel will:

    • Install dependencies using pnpm
    • Build the Vite frontend (static files)
    • Package the API functions from the api/ directory as serverless functions
    • Deploy everything to a global CDN with automatic HTTPS

    The deployment typically takes 1-2 minutes. Once complete, Vercel provides a live URL for your application.

How Vercel Serves Your Application

Vercel automatically handles both parts of your full-stack application:

  • Static Frontend: The built Vite application (from the dist/ directory) is served from Vercel's global CDN. This includes all your React components, styles, and client-side JavaScript. The CDN ensures fast load times for users worldwide.

  • Serverless API Functions: Each file in the api/ directory becomes an independent serverless function. When a request comes to /api/decks, /api/cards, or any other API endpoint, Vercel routes it to the corresponding serverless function. These functions scale automatically based on traffic—you never manage servers or worry about capacity.

This architecture provides the best of both worlds: fast static content delivery and scalable backend API endpoints, all deployed together with a single command.

Update Auth0 URLs

After your first deployment, update your Auth0 application settings to include the production URLs:

  1. Go to Auth0 Dashboard → Applications → your Single Page Application → Settings
  2. Add your Vercel deployment URL to:
    • Allowed Callback URLs: https://your-app.vercel.app (replace with your actual URL)
    • Allowed Logout URLs: https://your-app.vercel.app
    • Allowed Web Origins: https://your-app.vercel.app
  3. Save the changes

Tip: Keep your local development URLs (http://localhost:3000) in these lists so you can continue local development. You can have multiple URLs separated by commas.

Continuous Deployment

Once connected, Vercel automatically deploys your application whenever you push changes to your Git repository:

  • Production deployments: Pushes to your main/master branch trigger production deployments
  • Preview deployments: Pushes to other branches or pull requests create preview deployments with unique URLs for testing

This continuous deployment workflow means you can focus on writing code—Vercel handles the rest.

Troubleshooting

If you encounter issues while setting up or running the application, this section provides solutions to common problems, particularly related to Auth0 authentication configuration.

Auth0 Common Issues

Auth0 configuration can be tricky, especially when setting up the Custom API and Single Page Application for the first time. Below are the most common errors and their solutions.

Service Not Found Error

Error message:

access_denied: Service not found: https://…us.auth0.com/

Cause: Your VITE_AUTH0_AUDIENCE (and AUTH0_AUDIENCE) is set to your tenant/issuer URL instead of a Custom API Identifier. This is not valid.

Solution:

  1. In Auth0, go to Applications → APIs → Create API
  2. Set an Identifier (for example: https://flashcards-api)
  3. Use that exact string as the audience in your environment variables:
    • VITE_AUTH0_AUDIENCE=https://flashcards-api
    • AUTH0_AUDIENCE=https://flashcards-api
  4. Keep VITE_AUTH0_DOMAIN as your-tenant.us.auth0.com and AUTH0_ISSUER_BASE_URL as https://your-tenant.us.auth0.com/
  5. Only the audience values change to the new API Identifier—do not change the domain or issuer URL

Client Not Authorized Error

Error message:

invalid_request: Client "…" is not authorized to access resource server "https://flashcards-api"

Cause: Auth0 is refusing to issue an access token for your Custom API to your Single Page Application. This typically happens when the SPA is not authorized to request tokens for the API.

Solution (follow these steps in order):

  1. Verify same tenant: The Client ID in VITE_AUTH0_CLIENT_ID and the API (Identifier https://flashcards-api) must be in the same Auth0 tenant (same VITE_AUTH0_DOMAIN / AUTH0_ISSUER_BASE_URL).

  2. Verify application type: Your application in Auth0 must be Single Page Application (not Regular Web Application or Native unless you specifically need that).

  3. Authorize API access for the SPA:

    • Open Applications → Applications → your SPA → APIs tab
    • For the https://flashcards-api API, set User Access to Authorized or All
    • This creates the Auth0 client grant that allows your SPA to request access tokens for that API
  4. Disable RBAC for initial testing:

    • Open Applications → APIs → your API → Settings
    • Turn off Enable RBAC and Add Permissions in the Access Token for your first test
    • If RBAC is enabled, you'll need to configure roles, permissions, and client grants before turning it back on
  5. Re-authorize in the browser:

    • After changing API settings, clear site data for localhost:3000 (or use a private/incognito window)
    • Sign in again so you're not reusing a broken consent/state from the previous configuration

Callback URL Mismatch Error

Error message:

redirect_uri is not in the list of allowed callback URLs

Cause: Auth0 compares the exact string your application sends (from VITE_AUTH0_REDIRECT_URI in .env, or the page origin if unset) against the allowed callback URLs configured in your Auth0 application settings. Even small differences like http vs https, port numbers, or trailing slashes will cause this error.

Solution:

  1. Check the exact redirect URI your application is using:

    • If VITE_AUTH0_REDIRECT_URI is set in .env, use that exact value
    • If not set, the application uses the page origin (e.g., http://localhost:3000)
  2. In Auth0 Dashboard → Applications → your SPA → Settings, configure:

    • Allowed Callback URLs: Add the exact redirect URI, character-for-character
      • For pnpm dev:full (Vercel dev): http://localhost:3000
      • For pnpm dev (Vite only): http://localhost:5173
      • Important: Match the scheme (http vs https), port, and trailing slash exactly
    • Allowed Logout URLs: Add the same origin(s) (e.g., http://localhost:3000 and http://localhost:5173)
    • Allowed Web Origins: Add the same origin(s) for CORS support
  3. Save the changes in Auth0

  4. Try again in a private/incognito window to avoid cached authentication state

Tip: If you're not sure which port your development server is using, check the terminal output when you run pnpm dev:full or pnpm dev. Add all development URLs you use to the allowed lists.

401 Unauthorized API Errors

Symptom: The frontend loads successfully and you can log in, but API calls to /api/* endpoints return 401 Unauthorized errors. You may see "Unauthorized" messages in the UI when trying to load decks or cards.

Cause: The API serverless functions are rejecting the bearer token sent by the frontend. This typically happens when the backend Auth0 configuration is missing or incorrect.

Common causes and solutions:

1. Missing backend environment variables in Vercel:

The API runs in serverless functions and needs its own Auth0 configuration. The VITE_* environment variables are only available to the frontend build—they are not accessible to the API at runtime.

Solution:

  • In your Vercel project, go to Settings → Environment Variables
  • Add these variables for Production, Preview, and Development:
    • AUTH0_ISSUER_BASE_URL: Your Auth0 tenant URL (e.g., https://your-tenant.us.auth0.com/)
      • Important: Include the trailing slash
    • AUTH0_AUDIENCE: The Custom API Identifier (e.g., https://flashcards-api)
      • Must match the same value as VITE_AUTH0_AUDIENCE
  • Redeploy your application after adding these variables

2. Invalid auth token (issuer or audience mismatch):

Error in logs:

Invalid auth token

Cause: The token verification is failing because the issuer or audience doesn't match what the API expects.

Solution:

  • Verify AUTH0_ISSUER_BASE_URL is exactly https://YOUR_TENANT.us.auth0.com/ (with trailing slash)
  • Verify AUTH0_AUDIENCE exactly matches the Custom API Identifier from Auth0
  • Verify AUTH0_AUDIENCE matches what the frontend requests (VITE_AUTH0_AUDIENCE)
  • For detailed error logging, add AUTH0_DEBUG=1 to your .env file (local development only)
    • This will log the full verification error with a detail field explaining exactly what went wrong

3. Running Vite dev server without API:

Cause: If you run pnpm dev (Vite only), the API endpoints at /api/* are not available. Requests to /api/decks will fail or return unexpected errors.

Solution:

  • Always use pnpm dev:full for local development
  • This runs Vercel's development server, which serves both the frontend and the API functions
  • The frontend will be available at http://localhost:3000 with working API endpoints

Debugging tips:

  • Check the browser's Network tab to see the exact error response from the API
  • Check Vercel deployment logs or local terminal output for server-side error messages
  • Verify you can see the access token in the browser's Application/Storage tab (it should be stored by Auth0)
  • Use AUTH0_DEBUG=1 in local .env to get detailed token verification errors

Contributing

We welcome contributions from developers of all skill levels! Whether you're fixing a bug, adding a feature, improving documentation, or just learning, your contributions are valued and appreciated. This project is a great place to practice your skills, learn new technologies, and collaborate with others.

How to Contribute

There are many ways to contribute to this project:

  • Report bugs you encounter while using the application
  • Request features that would make the application more useful
  • Submit pull requests with bug fixes or new features
  • Improve documentation to help others understand and use the project
  • Review pull requests and provide feedback to other contributors
  • Share the project with others who might find it useful

Reporting Bugs

Found a bug? We appreciate you taking the time to report it! Good bug reports help us understand, reproduce, and fix issues quickly.

Before reporting a bug:

  • Check the Troubleshooting section to see if there's already a solution
  • Search existing GitHub Issues to see if someone else has already reported it
  • Make sure you're using the latest version of the application

When reporting a bug, please include:

  1. Clear description: Explain what happened and what you expected to happen instead

    • Example: "When I click 'Delete Deck', the deck is not removed from the list"
  2. Steps to reproduce: Provide a numbered list of steps that consistently trigger the bug

    • Example:
      1. Log in to the application
      2. Create a new deck called "Test Deck"
      3. Click the delete button next to "Test Deck"
      4. Observe that the deck remains in the list
  3. Environment information: Help us understand your setup

    • Browser: Chrome 120, Firefox 121, Safari 17, etc.
    • Operating System: Windows 11, macOS 14, Ubuntu 22.04, etc.
    • Device: Desktop, mobile, tablet (if relevant)
    • Deployment: Local development (pnpm dev:full) or production (Vercel URL)
  4. Expected behavior: What should have happened?

    • Example: "The deck should be removed from the list immediately"
  5. Actual behavior: What actually happened?

    • Example: "The deck remains in the list, and no error message is shown"
  6. Screenshots or error messages (if applicable): Visual evidence helps us understand the issue faster

    • Browser console errors (press F12 → Console tab)
    • Network errors (press F12 → Network tab)
    • Screenshots showing the unexpected behavior

Example bug report:

Bug: Deck deletion doesn't work

Description: When I try to delete a deck, it remains in the deck list and doesn't get removed.

Steps to reproduce:

  1. Log in to the application
  2. Create a new deck called "Test Deck"
  3. Click the delete button next to "Test Deck"
  4. Observe that the deck remains in the list

Expected behavior: The deck should be removed from the list immediately after clicking delete.

Actual behavior: The deck remains in the list, and no error message is shown.

Environment:

Console errors: DELETE /api/decks/123 failed with 500 Internal Server Error

The more details you provide, the easier it is for us to fix the bug. Don't worry about providing too much information—we'd rather have more context than not enough!

Requesting Features

Have an idea for a new feature or improvement? We'd love to hear it! Feature requests help us understand what users need and prioritize development efforts.

Before requesting a feature:

  • Check existing GitHub Issues to see if someone else has already suggested it
  • Consider whether the feature aligns with the project's goals (a flashcard application focused on spaced repetition learning)

When requesting a feature, please include:

  1. Clear description: Explain what you'd like to see added or changed

    • Example: "Add support for images on flashcards"
  2. Use case: Describe the problem this feature would solve or the benefit it would provide

    • Example: "I'm learning anatomy and need to include diagrams on my flashcards. Currently, I can only add text, which makes it difficult to study visual concepts."
  3. Proposed solution (optional): If you have ideas about how the feature could work, share them

    • Example: "Add an image upload button when creating or editing a card. The image would display on the front or back of the card during reviews."
  4. Alternatives considered (optional): Have you tried any workarounds? Are there other ways to solve the problem?

    • Example: "I've tried putting image URLs in the card text, but they don't render as images—just as plain text links."
  5. Priority (optional): How important is this feature to you?

    • Example: "This would be very helpful for my studies, but I can work around it for now by using text descriptions."

Example feature request:

Feature Request: Add image support to flashcards

Description: Allow users to upload and display images on flashcards (front or back).

Use case: I'm studying anatomy and need to include diagrams and illustrations on my flashcards. Currently, I can only add text, which makes it difficult to learn visual concepts like muscle groups, organ systems, and bone structures.

Proposed solution: Add an image upload button when creating or editing a card. Users could upload an image file (PNG, JPG, GIF) and choose whether it appears on the front or back of the card. During reviews, the image would display alongside or instead of text.

Alternatives considered: I've tried putting image URLs in the card text, but they don't render as images—just as plain text links. I've also tried using emoji, but they're not detailed enough for anatomy diagrams.

Priority: This would be very helpful for my studies and would make the application much more versatile for visual learners.

Feature requests are valuable even if you're not sure how to implement them. We appreciate your ideas and will consider them carefully!

Note: Not all feature requests can be implemented immediately, but we'll review each one and provide feedback. If you're interested in implementing the feature yourself, let us know in the issue—we're happy to guide you through the process!

Development Workflow

Ready to contribute code? This section walks you through the complete process of forking the repository, setting up your development environment, making changes, and submitting a pull request.

1. Fork the Repository

Create your own copy of the repository so you can make changes without affecting the original project:

  1. Go to the repository on GitHub
  2. Click the "Fork" button in the top-right corner
  3. GitHub will create a copy of the repository under your account (e.g., your-username/flashcards)

Why fork? Forking creates an independent copy where you can experiment freely. You'll submit your changes back to the original repository via a pull request.

2. Clone Your Fork

Download your forked repository to your local machine:

git clone https://github.com/your-username/flashcards.git
cd flashcards

Replace your-username with your actual GitHub username.

3. Set Up Your Development Environment

Follow the complete setup instructions in the Getting Started section to configure your local environment. This includes:

  • Installing prerequisites (Node.js, pnpm)
  • Configuring Auth0 authentication
  • Setting up environment variables (.env file)
  • Installing dependencies (pnpm install)
  • Running database migrations (pnpm db:migrate)
  • Starting the development server (pnpm dev:full)

Important: Make sure the application runs successfully on your machine before making changes. This ensures you're starting from a working state and can identify if your changes introduce any issues.

4. Create a Feature Branch

Create a new branch for your changes. Never work directly on the main or master branch:

git checkout -b feature/your-feature-name

Branch naming conventions:

  • feature/add-image-support - for new features
  • fix/deck-deletion-bug - for bug fixes
  • docs/update-readme - for documentation changes
  • refactor/simplify-auth - for code refactoring

Use descriptive names that clearly indicate what the branch contains. This helps reviewers understand your changes at a glance.

5. Make Your Changes

Now you can write code, fix bugs, or update documentation! As you work:

  • Write clear, readable code: Follow existing patterns and conventions in the codebase
  • Keep changes focused: Each branch should address one feature or bug, not multiple unrelated changes
  • Test your changes: Run the application locally and verify everything works as expected
  • Write or update tests: Add tests for new features or update existing tests if you change behavior (see Code Style and Testing)

Tip: Commit your changes frequently with clear commit messages. Small, focused commits are easier to review and debug if something goes wrong.

6. Commit Your Changes

Stage and commit your changes with descriptive commit messages:

git add .
git commit -m "Add image upload support to flashcards"

Writing good commit messages:

  • Use the imperative mood ("Add feature" not "Added feature" or "Adds feature")
  • Keep the first line under 50 characters
  • Provide additional context in the commit body if needed
  • Reference issue numbers if applicable (e.g., "Fix deck deletion bug (#123)")

Example commit messages:

  • ✅ Good: Fix deck deletion not removing cards
  • ✅ Good: Add image upload to card creation form
  • ✅ Good: Update README with deployment instructions
  • ❌ Bad: Fixed stuff
  • ❌ Bad: WIP
  • ❌ Bad: asdfasdf

7. Push Your Changes

Push your feature branch to your forked repository on GitHub:

git push origin feature/your-feature-name

If this is the first time pushing this branch, Git will set up tracking automatically. Subsequent pushes can use just git push.

8. Open a Pull Request

Submit your changes for review by opening a pull request (PR):

  1. Go to your forked repository on GitHub (https://github.com/your-username/flashcards)
  2. GitHub will usually show a banner suggesting you open a pull request for your recently pushed branch
  3. Click "Compare & pull request" (or go to the "Pull requests" tab and click "New pull request")
  4. Select the base repository and branch:
    • Base repository: The original repository (e.g., original-owner/flashcards)
    • Base branch: Usually main or master
    • Head repository: Your fork (e.g., your-username/flashcards)
    • Compare branch: Your feature branch (e.g., feature/your-feature-name)
  5. Fill out the pull request form:
    • Title: A clear, concise summary of your changes (under 70 characters)
      • Example: "Add image upload support to flashcards"
    • Description: Provide details about your changes (see below)
  6. Click "Create pull request"

Writing a good pull request description:

A well-written PR description helps reviewers understand your changes quickly and thoroughly. Include:

  1. Summary: What does this PR do?

    • Example: "This PR adds support for uploading images to flashcards. Users can now attach images to the front or back of cards during creation or editing."
  2. Motivation: Why is this change needed?

    • Example: "Many users study visual subjects like anatomy, geography, or art history and need to include images on their flashcards. This feature makes the application more versatile for visual learners."
  3. Changes made: What did you modify?

    • Example:
      • Added image upload component to card creation/edit form
      • Updated card schema to include optional imageUrl field
      • Modified review session to display images alongside text
      • Added image storage using Vercel Blob Storage
  4. Testing: How did you verify your changes work?

    • Example:
      • Tested image upload with PNG, JPG, and GIF files
      • Verified images display correctly during review sessions
      • Confirmed existing cards without images still work
      • Added unit tests for image upload component
  5. Screenshots (if applicable): Visual evidence of your changes

    • Include before/after screenshots for UI changes
    • Show the feature in action
  6. Related issues: Link to any related bug reports or feature requests

    • Example: "Closes #42" or "Fixes #123"

Example pull request description:

Summary

This PR adds support for uploading images to flashcards. Users can now attach images to the front or back of cards during creation or editing.

Motivation

Many users study visual subjects like anatomy, geography, or art history and need to include images on their flashcards (#42). This feature makes the application more versatile for visual learners.

Changes Made

  • Added image upload component to card creation/edit form
  • Updated card schema to include optional imageUrl field
  • Modified review session to display images alongside text
  • Added image storage using Vercel Blob Storage
  • Updated API endpoints to handle image uploads

Testing

  • ✅ Tested image upload with PNG, JPG, and GIF files
  • ✅ Verified images display correctly during review sessions
  • ✅ Confirmed existing cards without images still work
  • ✅ Added unit tests for image upload component
  • ✅ All existing tests pass

Screenshots

[Include screenshots showing the image upload UI and images displaying during review]

Related Issues

Closes #42

9. Respond to Review Feedback

After submitting your pull request, maintainers or other contributors will review your code. They may:

  • Approve the PR: Your changes look good and will be merged!
  • Request changes: They'll suggest improvements or ask questions about your implementation
  • Leave comments: They may point out issues, suggest alternatives, or ask for clarification

How to respond to feedback:

  1. Read feedback carefully: Understand what reviewers are asking for before making changes
  2. Ask questions: If feedback is unclear, ask for clarification—reviewers want to help!
  3. Make requested changes: Update your code based on the feedback
  4. Commit and push: Add new commits to your feature branch with the changes
    git add .
    git commit -m "Address review feedback: improve error handling"
    git push origin feature/your-feature-name
  5. Respond to comments: Let reviewers know you've addressed their feedback
    • Example: "Good catch! I've updated the error handling as you suggested."
  6. Be patient and respectful: Code review is a collaborative process—everyone wants to improve the code

Note: New commits to your feature branch automatically appear in the pull request. You don't need to close and reopen the PR.

10. Celebrate! 🎉

Once your pull request is approved and merged, your changes become part of the project! Your contribution will help other users and developers, and you'll be listed as a contributor to the project.

What happens after merge:

  • Your changes are now in the main branch of the original repository
  • The feature branch in the original repository is usually deleted automatically
  • You can safely delete your local feature branch:
    git checkout main
    git pull upstream main  # Update your local main branch
    git branch -d feature/your-feature-name  # Delete the feature branch
  • You can delete the feature branch from your fork on GitHub (optional)

Thank you for contributing! Every contribution, no matter how small, makes this project better. We appreciate your time and effort.

Code Style and Testing

To maintain code quality and consistency across the project, please follow these guidelines when contributing code. These practices help ensure the codebase remains readable, maintainable, and reliable.

Code Style Guidelines

TypeScript Best Practices

  • Use TypeScript throughout: All source files should use TypeScript (.ts for modules, .tsx for React components). Avoid using any types—prefer explicit types or unknown when the type is truly dynamic.

  • Define interfaces for complex types: Create interfaces or type aliases for objects with multiple properties. This improves readability and enables better IDE support.

    // Good: Clear interface definition
    interface UseKeyboardShortcutsOptions {
      revealed: boolean;
      done: boolean;
      onReveal: () => void;
      onGrade: (grade: 0 | 1 | 2 | 3) => void;
    }
    
    // Avoid: Inline object types in function signatures
    function useKeyboardShortcuts(options: { revealed: boolean; done: boolean; ... }) { }
  • Use meaningful variable and function names: Names should clearly describe what the variable holds or what the function does. Prefer descriptive names over abbreviations.

    // Good
    const reviewLoadingDeckId = useState<string | null>(null);
    function formatCardCount(count: number) { }
    
    // Avoid
    const rldi = useState<string | null>(null);
    function fmtCnt(c: number) { }
  • Add JSDoc comments for exported functions and hooks: Document the purpose, parameters, and return values of public APIs. This helps other developers understand how to use your code.

    /**
     * Custom hook for handling keyboard shortcuts in the flashcard review session.
     * 
     * Keyboard shortcuts:
     * - Left/Right arrows: Navigate between cards
     * - Space/Enter: Reveal answer (when front showing)
     * - 1-4: Grade card (when back showing)
     * 
     * Shortcuts are disabled when typing in input fields.
     */
    export function useKeyboardShortcuts(options: UseKeyboardShortcutsOptions): void {
      // Implementation...
    }

React Component Patterns

  • Use functional components with hooks: All React components should be functional components using hooks (useState, useEffect, useCallback, etc.). Avoid class components.

  • Extract reusable logic into custom hooks: If you find yourself duplicating stateful logic across components, extract it into a custom hook (prefix with use).

  • Keep components focused: Each component should have a single, clear responsibility. If a component is doing too much, consider breaking it into smaller components.

  • Use TypeScript for props: Define prop types using TypeScript interfaces, not PropTypes.

    interface CardFormProps {
      onCreate: (card: CardDraft) => Promise<void>;
      fieldClass: string;
      btnPrimaryClass: string;
    }
    
    function CardForm({ onCreate, fieldClass, btnPrimaryClass }: CardFormProps) {
      // Implementation...
    }
  • Prefer named exports over default exports: Named exports make refactoring easier and provide better IDE support.

    // Good
    export function KeyboardShortcutsReference() { }
    
    // Avoid
    export default function KeyboardShortcutsReference() { }

Code Organization

  • Co-locate related files: Keep components, hooks, and tests together in the same directory. For example, useKeyboardShortcuts.ts, useKeyboardShortcuts.test.ts, and KeyboardShortcutsReference.tsx all live in src/review/.

  • Use consistent file naming:

    • Components: PascalCase.tsx (e.g., ReviewSession.tsx)
    • Hooks: camelCase.ts (e.g., useKeyboardShortcuts.ts)
    • Tests: fileName.test.ts or fileName.test.tsx (e.g., useKeyboardShortcuts.test.ts)
  • Group imports logically: Organize imports in this order:

    1. External dependencies (React, libraries)
    2. Internal modules (utilities, types)
    3. Relative imports (components, hooks)
    // External dependencies
    import { useEffect, useState } from 'react';
    import { useAuth0 } from '@auth0/auth0-react';
    
    // Internal modules
    import { createCard, listCards } from './api';
    import type { Card, Deck } from './types';
    
    // Relative imports
    import { ReviewSession } from './review/ReviewSession';

General Code Quality

  • Follow existing patterns: Before writing new code, look at similar existing code in the project and follow the same patterns and conventions.

  • Keep functions small and focused: Each function should do one thing well. If a function is getting long (more than 30-40 lines), consider breaking it into smaller functions.

  • Use early returns: Prefer early returns for error cases or edge conditions rather than deeply nested if statements.

    // Good: Early return for edge case
    function isInputField(element: EventTarget | null): boolean {
      if (!(element instanceof HTMLElement)) {
        return false;
      }
      
      const tagName = element.tagName.toLowerCase();
      return tagName === 'input' || tagName === 'textarea';
    }
    
    // Avoid: Deeply nested conditions
    function isInputField(element: EventTarget | null): boolean {
      if (element instanceof HTMLElement) {
        const tagName = element.tagName.toLowerCase();
        if (tagName === 'input' || tagName === 'textarea') {
          return true;
        }
      }
      return false;
    }
  • Handle errors appropriately: Use try-catch blocks for async operations and provide meaningful error messages to users.

  • Avoid magic numbers: Use named constants for values that have meaning.

    // Good
    const GRADE_AGAIN = 0;
    const GRADE_HARD = 1;
    const GRADE_GOOD = 2;
    const GRADE_EASY = 3;
    
    // Avoid
    if (grade === 0) { /* ... */ }

Testing Requirements

All new features and bug fixes should include appropriate tests. This project uses Vitest for unit testing and React Testing Library for component testing.

When to Write Tests

  • New features: Write tests for all new functions, hooks, and components
  • Bug fixes: Add a test that reproduces the bug, then fix the code to make the test pass
  • Refactoring: Ensure existing tests still pass after refactoring

Running Tests

# Run all tests once
pnpm test

# Run tests in watch mode (re-runs on file changes)
pnpm test:watch

Writing Unit Tests

Unit tests verify that individual functions and hooks work correctly in isolation.

Example: Testing a custom hook

import { renderHook } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { useKeyboardShortcuts } from './useKeyboardShortcuts';

describe('useKeyboardShortcuts', () => {
  it('should call onNext when right arrow pressed', () => {
    const onNext = vi.fn();
    renderHook(() => useKeyboardShortcuts({
      revealed: false,
      done: false,
      canNext: true,
      onNext,
      // ... other required options
    }));

    const event = new KeyboardEvent('keydown', { key: 'ArrowRight' });
    window.dispatchEvent(event);

    expect(onNext).toHaveBeenCalledTimes(1);
  });
});

Writing Component Tests

Component tests verify that React components render correctly and respond to user interactions.

Example: Testing a component

import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { KeyboardShortcutsReference } from './KeyboardShortcutsReference';

describe('KeyboardShortcutsReference', () => {
  it('should render the component title', () => {
    render(<KeyboardShortcutsReference />);
    expect(screen.getByText('Keyboard Shortcuts')).toBeInTheDocument();
  });

  it('should display all keyboard shortcuts', () => {
    render(<KeyboardShortcutsReference />);
    
    // Check for specific shortcuts
    expect(screen.getByText('←')).toBeInTheDocument();
    expect(screen.getByText('→')).toBeInTheDocument();
    expect(screen.getByText('Space')).toBeInTheDocument();
  });
});

Testing Best Practices

  • Test behavior, not implementation: Focus on what the code does from a user's perspective, not how it does it internally. This makes tests more resilient to refactoring.

  • Use descriptive test names: Test names should clearly describe what is being tested and what the expected outcome is.

    // Good: Clear description of what's being tested
    it('should call onNext when right arrow pressed and canNext is true', () => { });
    
    // Avoid: Vague or unclear description
    it('should work', () => { });
    it('test navigation', () => { });
  • Arrange-Act-Assert pattern: Structure tests with three clear sections:

    1. Arrange: Set up test data and conditions
    2. Act: Execute the code being tested
    3. Assert: Verify the expected outcome
    it('should call onGrade when number key pressed and back showing', () => {
      // Arrange: Set up the hook with revealed state
      const onGrade = vi.fn();
      renderHook(() => useKeyboardShortcuts({
        revealed: true,
        onGrade,
        // ... other options
      }));
    
      // Act: Simulate pressing the "1" key
      const event = new KeyboardEvent('keydown', { key: '1' });
      window.dispatchEvent(event);
    
      // Assert: Verify onGrade was called with correct argument
      expect(onGrade).toHaveBeenCalledWith(0);
    });
  • Test edge cases: Don't just test the happy path—test error conditions, boundary values, and edge cases.

    it('should not call onNext when canNext is false', () => { });
    it('should ignore shortcuts when typing in input field', () => { });
    it('should handle empty review queue', () => { });
  • Use mocks for external dependencies: Mock API calls, timers, and other external dependencies to keep tests fast and isolated.

    import { vi } from 'vitest';
    
    const onReveal = vi.fn();  // Mock function
  • Clean up after tests: Use beforeEach and afterEach to reset state between tests.

    import { beforeEach, afterEach, vi } from 'vitest';
    
    describe('MyComponent', () => {
      beforeEach(() => {
        // Reset mocks before each test
        vi.clearAllMocks();
      });
      
      afterEach(() => {
        // Clean up after each test
        vi.restoreAllMocks();
      });
    });
  • Keep tests focused: Each test should verify one specific behavior. If you find yourself writing multiple assertions for different behaviors, split them into separate tests.

Test Coverage

While we don't enforce strict coverage percentages, aim to test:

  • All exported functions and hooks
  • All user-facing component behaviors
  • All edge cases and error conditions
  • Critical business logic (e.g., SM-2 algorithm, authentication)

Before Submitting Your Pull Request

  1. Run all tests: Ensure all tests pass with pnpm test
  2. Add tests for your changes: New features should include tests
  3. Update existing tests if needed: If you change behavior, update the corresponding tests
  4. Check for TypeScript errors: Run pnpm build to verify there are no type errors

If you're unsure about how to test something, look at existing tests in the codebase for examples, or ask for guidance in your pull request—we're happy to help!

Getting Help

Have questions or need guidance while contributing? We're here to help! Don't hesitate to reach out—everyone was new to the project at some point, and we want to make contributing as smooth as possible.

Where to Get Help

GitHub Discussions

The best place for general questions, feature discussions, and getting to know the community:

  • Q&A: Ask questions about the codebase, architecture, or how to implement a feature
  • Ideas: Discuss potential features or improvements before opening an issue
  • Show and Tell: Share what you're working on or get feedback on your approach

Visit the Discussions tab on GitHub to start a conversation.

GitHub Issues

For specific bugs, feature requests, or technical problems:

  • Comment on existing issues: If you're working on an issue or have questions about it, leave a comment
  • Open a new issue: If you encounter a problem or have a question that doesn't fit an existing issue, create a new one
  • Tag maintainers: Use @mentions to get attention from maintainers if you need help

Pull Request Comments

When you open a pull request, maintainers and other contributors will review your code and provide feedback:

  • Ask questions in PR comments: If you're unsure about something in your implementation, ask in the PR
  • Request reviews: Tag specific people if you want their input on your changes
  • Respond to feedback: Engage in the conversation—code review is collaborative!

Common Questions

"I'm new to [React/TypeScript/Vitest/etc.]. Can I still contribute?"

Absolutely! This project is a great place to learn. Start with small contributions like:

  • Fixing typos in documentation
  • Adding tests for existing features
  • Improving error messages
  • Implementing small, well-defined features

Don't be afraid to ask questions—we're happy to explain concepts and guide you through the process.

"I want to work on [feature/bug], but I'm not sure how to start."

Great! Here's how to get started:

  1. Comment on the issue: Let others know you're interested in working on it
  2. Ask for guidance: Request pointers on where to start or what approach to take
  3. Propose a plan: Share your initial thoughts on how you'd implement it and ask for feedback
  4. Start small: If the task is large, ask if it can be broken into smaller pieces

"I'm stuck on a problem and can't figure it out."

That's completely normal! Here's what to do:

  1. Describe what you've tried: Share the approaches you've attempted and what happened
  2. Include error messages: Copy the full error message or stack trace
  3. Share relevant code: Show the code you're working on (use code blocks in GitHub)
  4. Ask specific questions: Instead of "It doesn't work," try "I expected X but got Y. Why might that be?"

The more context you provide, the easier it is for others to help you.

"My pull request has been open for a while with no response."

Maintainers are often busy, and sometimes PRs slip through the cracks. If your PR hasn't received feedback:

  1. Wait at least 3-5 days: Give maintainers time to review
  2. Add a polite comment: Ping the PR with a friendly reminder (e.g., "Hi! Just checking if anyone has had a chance to review this. Happy to make any changes needed!")
  3. Check if CI is passing: Make sure all tests and checks are passing—PRs with failing tests may be deprioritized

"I disagree with the feedback I received."

Code review is subjective, and disagreements happen. Here's how to handle them:

  1. Assume good intent: Reviewers want to help improve the code, not criticize you personally
  2. Ask for clarification: If you don't understand the feedback, ask the reviewer to explain their reasoning
  3. Explain your perspective: Share why you made the choice you did—there may be context the reviewer doesn't have
  4. Be open to compromise: Sometimes there are multiple valid approaches. Be willing to meet in the middle.
  5. Escalate if needed: If you can't reach agreement, ask another maintainer for their opinion

"I made a mistake in my PR. What should I do?"

Mistakes happen to everyone! Here's how to fix them:

  • Small mistakes (typos, formatting): Just push a new commit with the fix
  • Larger mistakes (wrong approach, breaking changes): Explain what went wrong in a comment and ask for guidance on how to fix it
  • Accidentally broke something: Don't panic! Explain what happened, and work with maintainers to resolve it

The important thing is to communicate openly and work together to fix the issue.

Tips for Getting Better Help

  • Be specific: "The test fails" is less helpful than "The test fails with 'Expected 1 but got 0' on line 42"
  • Show your work: Share what you've tried and what you've learned so far
  • Be patient: Maintainers and contributors are often volunteers with limited time
  • Be respectful: Everyone is here to help. Treat others with kindness and respect
  • Pay it forward: Once you've learned something, help others who have the same question!

Learning Resources

If you're new to the technologies used in this project, here are some helpful resources:

React

TypeScript

Testing

Tailwind CSS

Git & GitHub

Remember: The best way to learn is by doing. Don't be afraid to dive in, make mistakes, and ask questions. We're all here to learn and grow together!

About

Simple Flash Card App

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages