This file provides guidance to WARP (warp.dev) when working with code in this repository.
Memcode is an open-source flashcards application built with React frontend and Node.js/Express backend. It uses PostgreSQL for persistence and supports spaced repetition learning. The project also includes a secondary application called Meresei - a calendar app for non-24 sleep wake disorder - that runs on the same infrastructure using virtual host routing.
# Create/reset development database
make db-reset
# Drop database
make db-drop
# Run specific migration
make db-migrate
# Dump database for backup
make db-dump
# Restore from dump
make db-restore# Start backend development server with hot reload and debugging
make start
# Start frontend webpack compilation with hot reload
make frontend-webpack
# Both commands should be run simultaneously for full development# Run backend tests
make test-backend
# Run frontend tests
make test-frontend
# Reset test database
make test-db-resetNote on Testing: This project has minimal tests by design. Only a few critical utility functions have tests. Do not write new tests unless specifically requested - the project prioritizes rapid development and manual testing over comprehensive test coverage.
# Deploy to Heroku
make heroku-deploy
# Build for production (automatically run on Heroku)
make heroku-postbuild
# Access Heroku database console
make heroku-db-console
# Pull production database to local
make heroku-db-pullThe codebase hosts two applications using virtual host routing:
- Memcode (main flashcards app): Default routes and memcode.com
- Meresei (calendar for non-24 sleep wake disorder): Routed via vhost for meresei.com domains
Both applications are deployed on the same Heroku server but serve completely different purposes and user bases.
- Entry Point:
backend/index.js- Sets up vhost routing and starts server - API Layer: Dynamic controller routing via
/api/:controllerName.:methodName - Database: PostgreSQL with Knex (migrating from pg-promise)
- Models: Located in
backend/models/with dedicated model classes - Path Aliases: Uses
#~/for backend imports (configured in package.json)
Key backend concepts:
- Controllers follow naming pattern (e.g.,
CourseApi,UserApi) - Middleware pipeline: SSL redirect → CORS → authentication → error handling
- Spaced repetition algorithm for flashcard scheduling
- Database Migration: Currently transitioning from pg-promise to Knex query builder
All backend endpoints should use standardized response methods:
response.success(obj) // 200 status with data object
response.error(string) // 500 status with error message
response.validation(array) // 400 status with validation errorsThese are injected by the injectResponseTypes middleware.
- Entry Point:
frontend/index.js- React app with Redux store - Build System: Webpack with separate dev/production configs
- State Management: Redux with connected components
- Routing: React Router for SPA navigation
- Path Aliases: Uses
~/for frontend imports
Key frontend concepts:
- Page components in
frontend/pages/correspond to routes - Reusable components in
frontend/components/andfrontend/appComponents/ - API calls through centralized services in
frontend/api/ - CSS modules and SCSS for styling
The project uses CSS modules with SCSS for component styling. Follow these patterns:
// Single :local() wrapper per component
:local(.componentName) {
.childClass {
// styles
}
.anotherChild {
// nested styles
.deeplyNested {
// deep nesting is preferred
&:hover {
// pseudo-selectors
}
}
}
}import css from './index.scss';
// Only the main wrapper uses css object
<div className={css.componentName}>
{/* All nested elements use string classNames */}
<div className="childClass">
<div className="anotherChild">
<div className="deeplyNested">Content</div>
</div>
</div>
</div>- Single :local() wrapper - Only one per component file
- Deep nesting preferred - Unlike some CSS traditions, we embrace deep nesting for component isolation
- String classNames - Use string literals for nested classes, not
css.className - SCSS features - Use variables, mixins, and nesting as needed
- Component-scoped - All styles are automatically scoped to prevent conflicts
Core entities:
- Users: OAuth-based authentication (GitHub/Google)
- Courses: Flashcard collections with categories
- Problems: Individual flashcards with different types
- Learning Progress: Spaced repetition tracking with easiness factors
- Notifications: User activity notifications
- Legacy: pg-promise with raw SQL queries
- Current: Knex query builder with knex-stringcase for camelCase conversion
- Pattern: New API routes should use Knex, old ones gradually being migrated
- Copy
env.example.jstoenv.jsand fill required values - Set up PostgreSQL with user 'postgres' and password 'postgres'
- Run
make db-resetto create database schema - Run
npm installto install dependencies
- Start backend:
make start(runs on port 3000 with nodemon) - Start frontend build:
make frontend-webpack(watches for changes) - Access app at
http://localhost:3000
- ESLint configured with Airbnb style guide (extensive customizations in
.eslintrc.js) - Relaxed rules for development velocity over strict adherence
- Supports both class and functional React components
- DO NOT use
export const- Always use default exports or named exports in object form
- Minimal test coverage by design - only critical utility functions are tested
- Focus on manual testing and rapid iteration
- Do not write new tests unless explicitly requested
Uses OAuth providers (GitHub, Google) with JWT tokens for session management.
Images uploaded to AWS S3 with multer-s3 integration.
Service worker registered for production builds to enable offline flashcard review.
Single deployment supports both Memcode and Meresei apps through vhost routing, allowing domain-based application switching.
- Frontend: Webpack compilation to
backend/webpacked/for serving - Backend: ES modules with import/export syntax
- Production: Heroku-compatible with automatic builds
- Prefer dynamic routing:
/api/CourseApi.getPublicCoursesover traditional REST - Dynamic routing:
/api/CourseApi.getPublicCourses - Traditional REST:
/api/courses/:id(legacy pattern) - Both GET and POST supported for API calls
- Always use standardized response methods:
response.success(),response.error(), andresponse.validation()
// Success response (200)
response.success({ user: userData, token: jwtToken });
// Server error response (500)
response.error('Database connection failed');
// Validation error response (400)
response.validation([
'Username is required',
'Email must be valid',
'Password must be at least 6 characters'
]);- Always use Knex: New development should exclusively use Knex query builder from
#~/db/knex.js - Legacy routes: pg-promise queries (being phased out - do not use for new code)
- Snake_case in database, camelCase in application code via knex-stringcase
- Transaction support for complex operations
- Redux store with thunk middleware
- Connected components pattern
- Local component state for UI-only concerns
The codebase uses a consistent SPE pattern for managing async operations and API calls:
// State management
state = {
speApiCall: {} // Empty object initially
}
// API call with SPE dispatch
api.SomeApi.someMethod(
(speApiCall) => this.setState({ speApiCall }),
requestData
)
// SPE object structure:
// - status: 'request' | 'success' | 'failure' | undefined
// - payload: data (on success)
// - error: error message (on failure)Use the Loading component to handle different states automatically:
import Loading from '~/components/Loading';
// Basic usage - handles request/success/failure states
<Loading spe={this.state.speApiCall}>
Content to show on success
</Loading>
// With payload access
<Loading spe={this.state.speApiCall}>{(payload) =>
<div>{payload.someData}</div>
}</Loading>
// Show only specific states
<Loading enabledStatuses={['failure']} spe={this.state.speApiCall} />
<Loading enabledStatuses={['success', 'error']} spe={this.state.speApiCall} />Consistent form state management and input handling:
// Form state management
state = {
formState: {
field1: '',
field2: ''
}
}
// Input props helper
inputProps = () => ({
formState: this.state.formState,
updateFormState: (formState) => this.setState({ formState })
})
// Usage in render
<TextInput {...this.inputProps()} label="Field 1" name="field1" />
<TextInput {...this.inputProps()} label="Field 2" name="field2" type="email" />Combine forms with SPE pattern for clean submit handling:
handleSubmit = (event) => {
event.preventDefault();
// Prevent double submission
if (this.state.speSubmit.status === 'request') return;
api.SomeApi.submit(
(speSubmit) => {
this.setState({ speSubmit });
if (speSubmit.status === 'success') {
// Handle success (redirect, close modal, etc.)
}
},
this.state.formState
);
}
// In render - button state and error display
<Loading enabledStatuses={['failure']} spe={this.state.speSubmit} />
<button
disabled={this.state.speSubmit.status === 'request'}
type="submit"
>
{this.state.speSubmit.status === 'request' ? 'Loading...' : 'Submit'}
</button>- Use
makecommands rather than direct npm/node commands - Database resets are safe and fast for development
- Frontend hot reload works through webpack dev config
- Backend auto-restart via nodemon for file changes
- Manual testing is preferred over writing automated tests
- When creating new API endpoints, use Knex instead of pg-promise
- Always use the standardized response methods (
.success,.error,.validation)
Meresei is a separate application for managing non-24 sleep wake disorder calendars. It:
- Shares the same server infrastructure as Memcode
- Uses virtual host routing for domain separation
- Has its own frontend build system (
meresei/frontend/) - Is completely independent in terms of functionality and user base