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.
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
Stored in blob storage with key: flashcard_user_{username}
{
username: string,
decks: Deck[],
cards: Card[],
sessions: StudySession[],
stats: UserStats
}
{
id: string, // Generated unique ID
name: string, // Deck name
description: string, // Optional description
createdAt: string // ISO date string
}
{
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
}
{
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"
}
{
totalCards: number, // Total cards created
totalReviews: number, // Total reviews across all sessions
totalTime: number, // Total study time in seconds
averageAccuracy: number // Percentage (0-100)
}
The app uses a simplified version of the SuperMemo SM-2 algorithm, which is the foundation of Anki's spaced repetition system.
Users rate their recall on a 4-point scale:
Again (Failed): Didn't remember the answer
Hard (Difficult): Remembered with difficulty
Good (Normal): Remembered correctly with some effort
Easy (Perfect): Remembered instantly
Cards are considered "due" when:
new Date(card.nextReview) <= new Date()
Cards are presented in order of their nextReview date (earliest first).
POST /api/auth/login
{ username: string }UserData objectGET /api/decks
X-Username: stringDeck objectsPOST /api/decks
X-Username: string{ name: string, description?: string }DeckPUT /api/decks/:id
X-Username: string{ name?: string, description?: string }DeckDELETE /api/decks/:id
X-Username: string{ success: true }GET /api/cards/deck/:deckId
X-Username: stringCard objectsPOST /api/cards
X-Username: string{ deckId: string, front: string, back: string }CardPUT /api/cards/:id
X-Username: string{ front?: string, back?: string }CardDELETE /api/cards/:id
X-Username: string{ success: true }GET /api/study/:deckId
X-Username: stringCard objects (sorted by nextReview)POST /api/study/review
X-Username: string{ cardId: string, rating: number (1-4) }{ card: Card, reviewResult: ReviewResult }GET /api/stats
X-Username: string{ stats: UserStats, sessions: StudySession[] }POST /api/stats/sessions
X-Username: string{ deckId: string, duration: number, cardsReviewed: number, correctCount: number }StudySessionGET /api/backup
X-Username: stringUserData JSONPOST /api/backup/restore
X-Username: stringUserData JSON{ success: true, message: string }The app uses a view-based navigation system where only one view is active at a time:
Global state variables:
currentUser: Logged-in usernameuserData: Full user data objectcurrentDeckId: Active deckstudyCards: Cards in current study sessionsessionStartTime: Study session start timestampSession persistence:
sessionStorageAll API calls:
apiCall() wrapper functionX-Username header for authenticated requests/api/study/:deckId/api/study/review/api/stats/sessionsCard Flip Animation
perspective and rotateYbackface-visibility: hiddenResponsive Design
Each user's data is stored in a separate blob:
flashcard_user_{username}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.
Users can export their entire dataset as JSON and restore it later, enabling:
Decision: Use Blob Storage
Rationale:
Tradeoffs:
Decision: No password, just username
Rationale:
Tradeoffs:
Decision: Vanilla JavaScript
Rationale:
Tradeoffs:
Decision: Client-side view switching
Rationale:
Tradeoffs:
Decision: Simplified SM-2 with 4 ratings
Rationale:
Tradeoffs:
Decision: Store deckName in StudySession
Rationale:
Tradeoffs:
Suitable for:
Beyond this, consider:
shared/types.tsbackend/storage.ts if neededbackend/routes/frontend/index.htmlfrontend/index.jsonError configured)shared/ compatible with both browser and Denomain.tsThis 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.