• Townie
    AI
  • Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
jonmaim

jonmaim

flashcard

anki-style flash cards
Public
Like
flashcard
Home
Code
10
backend
4
frontend
3
shared
2
.vtignore
AGENTS.md
ARCHITECTURE.md
GETTING_STARTED.md
README.md
deno.json
H
main.ts
Branches
1
Pull requests
Remixes
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
ARCHITECTURE.md
Code
/
ARCHITECTURE.md
Search
…
ARCHITECTURE.md

Flashcard Learning App - Architecture Documentation

Overview

This is a full-featured flashcard learning application built for Val Town, implementing spaced repetition learning (SM-2 algorithm) with comprehensive statistics tracking and backup/restore capabilities.

System Architecture

Tech Stack

  • Backend Framework: Hono 4.0.2
  • Frontend: Vanilla JavaScript with TailwindCSS
  • Storage: Val Town Blob Storage (per-user isolation)
  • Runtime: Deno on Val Town serverless platform
  • Algorithm: Simplified SM-2 Spaced Repetition (Anki-style)

Project Structure

flashcard/
β”œβ”€β”€ main.ts                  # Entry point (exports backend/index.ts)
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ index.ts            # Hono app setup and route mounting
β”‚   β”œβ”€β”€ storage.ts          # Blob storage utilities
β”‚   └── routes/
β”‚       β”œβ”€β”€ auth.ts         # User authentication
β”‚       β”œβ”€β”€ decks.ts        # Deck CRUD operations
β”‚       β”œβ”€β”€ cards.ts        # Card CRUD operations
β”‚       β”œβ”€β”€ study.ts        # Spaced repetition study mode
β”‚       β”œβ”€β”€ stats.ts        # Statistics and session tracking
β”‚       └── backup.ts       # Data backup/restore
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ index.html          # Single-page application UI
β”‚   └── index.js            # Client-side logic and API calls
└── shared/
    └── types.ts            # Shared TypeScript types and SR algorithm

Data Models

UserData (Root Storage Object)

Stored in blob storage with key: flashcard_user_{username}

{ username: string, decks: Deck[], cards: Card[], sessions: StudySession[], stats: UserStats }

Deck

{ id: string, // Generated unique ID name: string, // Deck name description: string, // Optional description createdAt: string // ISO date string }

Card

{ id: string, // Generated unique ID deckId: string, // Parent deck reference front: string, // Question/prompt back: string, // Answer easeFactor: number, // SM-2 ease factor (default: 2.5) interval: number, // Days until next review nextReview: string, // ISO date string reviewCount: number // Total times reviewed }

StudySession

{ id: string, // Generated unique ID date: string, // ISO date string deckId: string, // Deck studied deckName: string, // Deck name (denormalized) duration: number, // Session duration in seconds cardsReviewed: number, // Total cards reviewed correctCount: number // Cards rated "Good" or "Easy" }

UserStats (Calculated)

{ totalCards: number, // Total cards created totalReviews: number, // Total reviews across all sessions totalTime: number, // Total study time in seconds averageAccuracy: number // Percentage (0-100) }

Spaced Repetition Algorithm

SM-2 Algorithm Implementation

The app uses a simplified version of the SuperMemo SM-2 algorithm, which is the foundation of Anki's spaced repetition system.

Initial Values

  • easeFactor: 2.5
  • interval: 0 (new cards are due immediately)
  • nextReview: Current date/time

Rating System

Users rate their recall on a 4-point scale:

  1. Again (Failed): Didn't remember the answer

    • Reset interval to 1 day
    • Reduce easeFactor by 0.2 (min: 1.3)
  2. Hard (Difficult): Remembered with difficulty

    • Multiply interval by 1.2
    • Reduce easeFactor by 0.15 (min: 1.3)
  3. Good (Normal): Remembered correctly with some effort

    • First review: 1 day
    • Second review: 6 days
    • Subsequent: interval Γ— easeFactor
  4. Easy (Perfect): Remembered instantly

    • First review: 4 days
    • Subsequent: interval Γ— easeFactor Γ— 1.3
    • Increase easeFactor by 0.15

Due Card Selection

Cards are considered "due" when:

new Date(card.nextReview) <= new Date()

Cards are presented in order of their nextReview date (earliest first).

Algorithm Benefits

  • Optimizes retention: Reviews cards just before you're likely to forget them
  • Reduces study time: Focuses on difficult cards, spaces out easy ones
  • Long-term retention: Proven effective for language learning, medical studies, etc.

API Endpoints

Authentication

POST /api/auth/login

  • Body: { username: string }
  • Returns: Full UserData object
  • Creates new user if doesn't exist

Deck Management

GET /api/decks

  • Headers: X-Username: string
  • Returns: Array of Deck objects

POST /api/decks

  • Headers: X-Username: string
  • Body: { name: string, description?: string }
  • Returns: Created Deck

PUT /api/decks/:id

  • Headers: X-Username: string
  • Body: { name?: string, description?: string }
  • Returns: Updated Deck

DELETE /api/decks/:id

  • Headers: X-Username: string
  • Returns: { success: true }
  • Cascades: Deletes all cards and sessions for the deck

Card Management

GET /api/cards/deck/:deckId

  • Headers: X-Username: string
  • Returns: Array of Card objects

POST /api/cards

  • Headers: X-Username: string
  • Body: { deckId: string, front: string, back: string }
  • Returns: Created Card

PUT /api/cards/:id

  • Headers: X-Username: string
  • Body: { front?: string, back?: string }
  • Returns: Updated Card

DELETE /api/cards/:id

  • Headers: X-Username: string
  • Returns: { success: true }

Study Mode

GET /api/study/:deckId

  • Headers: X-Username: string
  • Returns: Array of due Card objects (sorted by nextReview)

POST /api/study/review

  • Headers: X-Username: string
  • Body: { cardId: string, rating: number (1-4) }
  • Returns: { card: Card, reviewResult: ReviewResult }
  • Updates card's SM-2 parameters

Statistics

GET /api/stats

  • Headers: X-Username: string
  • Returns: { stats: UserStats, sessions: StudySession[] }

POST /api/stats/sessions

  • Headers: X-Username: string
  • Body: { deckId: string, duration: number, cardsReviewed: number, correctCount: number }
  • Returns: Created StudySession

Backup & Restore

GET /api/backup

  • Headers: X-Username: string
  • Returns: Full UserData JSON
  • Sets Content-Disposition header for download

POST /api/backup/restore

  • Headers: X-Username: string
  • Body: Full UserData JSON
  • Returns: { success: true, message: string }
  • Replaces all user data

Frontend Architecture

Single-Page Application Structure

The app uses a view-based navigation system where only one view is active at a time:

  1. Login View: Username entry
  2. Deck List View: Dashboard of all decks
  3. Deck Editor View: Create/edit deck
  4. Card Manager View: List and edit cards in a deck
  5. Study View: Spaced repetition study session
  6. Statistics View: Performance metrics and session history
  7. Backup View: Export/import data

State Management

Global state variables:

  • currentUser: Logged-in username
  • userData: Full user data object
  • currentDeckId: Active deck
  • studyCards: Cards in current study session
  • sessionStartTime: Study session start timestamp

Session persistence:

  • Username stored in sessionStorage
  • Auto-login on page reload

API Communication

All API calls:

  • Use apiCall() wrapper function
  • Include X-Username header for authenticated requests
  • Handle errors with user-friendly messages
  • Return parsed JSON responses

Study Session Flow

  1. Load due cards from /api/study/:deckId
  2. Start timer and initialize counters
  3. For each card:
    • Show front (question)
    • User flips card to see back (answer)
    • User rates recall (Again/Hard/Good/Easy)
    • Update card via /api/study/review
    • Track correct answers (Good/Easy)
  4. On completion:
    • Calculate duration and accuracy
    • Save session via /api/stats/sessions
    • Show results summary

UI Components

Card Flip Animation

  • CSS 3D transform with perspective and rotateY
  • Front and back faces with backface-visibility: hidden
  • Smooth 0.6s transition

Responsive Design

  • TailwindCSS utility classes
  • Grid layouts for deck cards
  • Mobile-friendly navigation

Storage Strategy

Per-User Isolation

Each user's data is stored in a separate blob:

  • Key pattern: flashcard_user_{username}
  • No user can access another user's data
  • Simple username-based authentication (suitable for personal/demo use)

Data Operations

Read: blob.getJSON(key) - Loads entire user object Write: blob.setJSON(key, data) - Saves entire user object

All mutations happen in-memory, then saved atomically.

Backup/Restore

Users can export their entire dataset as JSON and restore it later, enabling:

  • Data portability
  • Version control (manual)
  • Migration between Val Town accounts
  • Recovery from mistakes

Design Decisions & Tradeoffs

1. Blob Storage vs SQLite

Decision: Use Blob Storage

Rationale:

  • Simpler per-user isolation
  • No schema migrations needed
  • Faster reads (single blob fetch vs multiple queries)
  • Suitable for personal flashcard app scale

Tradeoffs:

  • No cross-user queries (not needed)
  • Must load entire user dataset
  • No incremental updates (acceptable for this scale)

2. Username-Only Authentication

Decision: No password, just username

Rationale:

  • Simplifies UX for personal/demo use
  • Val Town already provides account-level security
  • Focus on learning features, not auth complexity

Tradeoffs:

  • Not suitable for production multi-user app
  • Users should use unique usernames
  • Sufficient for personal use or trusted environments

3. Vanilla JavaScript vs React

Decision: Vanilla JavaScript

Rationale:

  • Lighter bundle size
  • Simpler debugging
  • No build step required
  • Easier to understand for learners

Tradeoffs:

  • More manual DOM manipulation
  • No component reusability
  • State management is manual

4. Client-Side Routing vs Server-Side

Decision: Client-side view switching

Rationale:

  • Single-page feel
  • No page reloads
  • Better UX for study sessions
  • Simpler state management

Tradeoffs:

  • All views loaded upfront
  • SEO not a concern for this app type

5. SM-2 Algorithm Variant

Decision: Simplified SM-2 with 4 ratings

Rationale:

  • Proven algorithm (used by Anki)
  • Simple to implement
  • Effective for most use cases
  • Users understand 4-point scale

Tradeoffs:

  • Not as sophisticated as SM-17+
  • No auto-tuning of parameters
  • Good enough for personal learning

6. Denormalized Statistics

Decision: Store deckName in StudySession

Rationale:

  • Preserve history if deck is renamed/deleted
  • Faster stats display (no joins)
  • Simpler queries

Tradeoffs:

  • Data duplication
  • Potential inconsistency (acceptable for historical data)

Performance Considerations

Optimization Strategies

  1. Single Blob Fetch: Load entire user data once on login
  2. In-Memory Mutations: Update local state, then save
  3. Lazy Loading: Only load cards when viewing/studying a deck
  4. Session Storage: Remember username between page loads
  5. Batch Updates: Save session data once at completion

Scalability Limits

Suitable for:

  • Up to ~1000 decks per user
  • Up to ~10,000 cards per user
  • Up to ~1000 sessions per user

Beyond this, consider:

  • Pagination
  • Incremental loading
  • SQLite for relational queries
  • Separate blob per deck

Future Enhancement Ideas

Algorithm Improvements

  • Implement Anki's FSRS algorithm
  • Add learning phase (new cards)
  • Configurable ease factor bounds
  • Review history analysis

Features

  • Deck sharing (export/import specific decks)
  • Image support in cards (Val Town AI image generation)
  • Audio pronunciation
  • Cloze deletions
  • Card tagging/filtering
  • Search functionality
  • Deck templates (languages, medicine, etc.)
  • Heatmap calendar of study activity
  • Review forecast

UX Improvements

  • Keyboard shortcuts
  • Dark mode
  • Progressive Web App (offline support)
  • Mobile app wrapper
  • Undo card deletion
  • Bulk card import (CSV)

Analytics

  • Retention graphs
  • Card difficulty distribution
  • Study time by deck
  • Streak tracking
  • Learning velocity

Development Guidelines

Adding New Features

  1. Data Model: Update types in shared/types.ts
  2. Storage: Update backend/storage.ts if needed
  3. API: Add routes in backend/routes/
  4. UI: Add view in frontend/index.html
  5. Logic: Implement in frontend/index.js
  6. Test: Manual testing in Val Town preview

Error Handling

  • Backend: Let errors bubble (Hono's onError configured)
  • Frontend: User-friendly alert messages
  • API errors: Always include descriptive error messages

Code Organization

  • Keep shared/ compatible with both browser and Deno
  • Use TypeScript types consistently
  • Comment complex algorithms
  • Maintain separation of concerns (storage/routes/UI)

Deployment

Val Town Deployment

  1. Upload project files to Val Town
  2. Set main entry point to main.ts
  3. Configure as HTTP Val
  4. No environment variables needed (blob storage is built-in)

Testing

  • Local: Run with Deno (requires Val Town SDK)
  • Preview: Use Val Town's preview feature
  • Production: Deploy to Val Town domain

Conclusion

This flashcard app demonstrates a complete full-stack application built for Val Town's serverless platform, implementing a proven learning algorithm with a clean, functional UI. The architecture prioritizes simplicity and usability while remaining extensible for future enhancements.

The use of spaced repetition makes it genuinely useful for learning, while the statistics tracking provides motivation and insight into learning progress. The backup/restore functionality ensures users never lose their hard work.

FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
Β© 2025 Val Town, Inc.