Developer Guide

Contributing to BrainMapRevision - Architecture, setup, and best practices

Table of Contents

1. Introduction

Welcome to the BrainMapRevision developer guide! This document will help you understand the project structure, set up your development environment, and contribute effectively.

Tech Stack

Project Goals

Development Philosophy

2. Development Setup

Prerequisites

Initial Setup

bash
# 1. Fork the repository on GitHub

# 2. Clone your fork
git clone https://github.com/YOUR-USERNAME/BrainMapRevision.git

# 3. Navigate to project directory
cd BrainMapRevision

# 4. Create a new branch for your feature
git checkout -b feature/your-feature-name

# 5. Open in your editor
code .

Supabase Configuration

  1. Create a Supabase account at supabase.com
  2. Create a new project
  3. Navigate to Settings → API
  4. Copy your project URL and anon/public key
  5. Update assets/js/supabase-config.js:
javascript
const SUPABASE_URL = 'https://your-project.supabase.co';
const SUPABASE_ANON_KEY = 'your-anon-key-here';

const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

Running Locally

Since this is a static site, you can use any local server:

bash
# Using Python 3
python -m http.server 8000

# Using Node.js (install live-server globally)
npm install -g live-server
live-server

# Using VS Code Live Server extension
# Right-click index.html → "Open with Live Server"

Navigate to http://localhost:8000 in your browser.

Recommended VS Code Extensions

3. Project Architecture

File Structure

plaintext
BrainMapRevision/
├── index.html              # Homepage
├── style.css               # Global styles
├── script.js               # Global scripts
├── pages/                  # All feature pages
│   ├── subjects.html
│   ├── past-papers.html
│   ├── revision-boards.html
│   ├── quizzes.html
│   └── ...
├── subjects/               # Subject-specific pages
├── key-topics/            # Topic deep-dives
├── exam-boards/           # Exam board pages
├── auth/                  # Authentication pages
├── assets/
│   ├── css/               # Component-specific CSS
│   ├── js/                # JavaScript modules
│   └── images/            # Images and icons
├── supabase/
│   ├── schema.sql         # Database schema
│   ├── seed.sql           # Initial data
│   └── functions/         # Database functions
├── docs/                  # Documentation
├── legal/                 # Legal pages
└── markdown/              # Project documentation

CSS Architecture

Styles are organized as follows:

CSS Variables

Use CSS custom properties defined in :root for consistent theming:

  • --bg-primary, --bg-secondary
  • --text-primary, --text-secondary
  • --accent-primary, --accent-secondary
  • --border-color, --card-bg

JavaScript Architecture

JavaScript is modular and organized by feature:

Component Pattern

Reusable components follow this pattern:

javascript
// Component: Quiz Card
class QuizCard {
    constructor(data) {
        this.question = data.question;
        this.options = data.options;
        this.correctAnswer = data.correctAnswer;
    }

    render() {
        return `
            <div class="quiz-card">
                <h3>${this.question}</h3>
                <div class="options">
                    ${this.renderOptions()}
                </div>
            </div>
        `;
    }

    renderOptions() {
        return this.options
            .map(opt => `<button class="option">${opt}</button>`)
            .join('');
    }
}

// Usage
const quiz = new QuizCard({
    question: "What is the capital of France?",
    options: ["London", "Paris", "Berlin", "Madrid"],
    correctAnswer: 1
});

document.getElementById('quiz-container').innerHTML = quiz.render();

4. Database Schema

Core Tables

users

sql
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(50) UNIQUE NOT NULL,
    role VARCHAR(20) DEFAULT 'student', -- 'student' or 'teacher'
    year_group VARCHAR(20),
    xp INTEGER DEFAULT 0,
    level INTEGER DEFAULT 1,
    brain_coins INTEGER DEFAULT 0,
    hint_tokens INTEGER DEFAULT 5,
    study_streak INTEGER DEFAULT 0,
    last_login TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW()
);

subjects

sql
CREATE TABLE subjects (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    icon VARCHAR(50),
    description TEXT,
    year_groups TEXT[] -- ['Year 7-9', 'Year 10-11']
);

past_paper_questions

sql
CREATE TABLE past_paper_questions (
    id SERIAL PRIMARY KEY,
    subject_id INTEGER REFERENCES subjects(id),
    exam_board VARCHAR(50) NOT NULL, -- 'AQA', 'Edexcel', etc.
    year INTEGER NOT NULL,
    paper_number VARCHAR(20),
    question_number VARCHAR(20),
    marks INTEGER,
    question_text TEXT NOT NULL,
    mark_scheme TEXT,
    examiner_comments TEXT,
    revision_guide_id INTEGER REFERENCES revision_guides(id),
    difficulty VARCHAR(20), -- 'Foundation' or 'Higher'
    topics TEXT[], -- ['Algebra', 'Equations']
    created_at TIMESTAMP DEFAULT NOW()
);

revision_guides

sql
CREATE TABLE revision_guides (
    id SERIAL PRIMARY KEY,
    subject_id INTEGER REFERENCES subjects(id),
    topic VARCHAR(200) NOT NULL,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL, -- Markdown format
    key_concepts TEXT[],
    worked_examples TEXT,
    common_mistakes TEXT,
    exam_tips TEXT,
    author_id UUID REFERENCES users(id),
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

user_progress

sql
CREATE TABLE user_progress (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES users(id),
    subject_id INTEGER REFERENCES subjects(id),
    topic VARCHAR(200),
    questions_attempted INTEGER DEFAULT 0,
    questions_correct INTEGER DEFAULT 0,
    mastery_percentage DECIMAL(5,2) DEFAULT 0.00,
    last_studied TIMESTAMP,
    total_study_time INTEGER DEFAULT 0, -- in minutes
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(user_id, subject_id, topic)
);

quizzes

sql
CREATE TABLE quizzes (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES users(id),
    subject_id INTEGER REFERENCES subjects(id),
    score INTEGER,
    total_questions INTEGER,
    xp_earned INTEGER,
    coins_earned INTEGER,
    hints_used INTEGER DEFAULT 0,
    completion_time INTEGER, -- in seconds
    completed_at TIMESTAMP DEFAULT NOW()
);

revision_boards

sql
CREATE TABLE revision_boards (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES users(id),
    title VARCHAR(200) NOT NULL,
    description TEXT,
    subject_id INTEGER REFERENCES subjects(id),
    content JSONB, -- Flexible content structure
    is_public BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

shop_items

sql
CREATE TABLE shop_items (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    category VARCHAR(50), -- 'theme', 'hint_pack', 'power_up', 'cosmetic'
    price INTEGER NOT NULL, -- in Brain Coins
    min_level INTEGER DEFAULT 1,
    image_url VARCHAR(255),
    properties JSONB -- Additional item data
);

user_inventory

sql
CREATE TABLE user_inventory (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES users(id),
    item_id INTEGER REFERENCES shop_items(id),
    purchased_at TIMESTAMP DEFAULT NOW(),
    is_equipped BOOLEAN DEFAULT FALSE,
    UNIQUE(user_id, item_id)
);

classrooms

sql
CREATE TABLE classrooms (
    id SERIAL PRIMARY KEY,
    teacher_id UUID REFERENCES users(id),
    name VARCHAR(200) NOT NULL,
    subject_id INTEGER REFERENCES subjects(id),
    year_group VARCHAR(20),
    invite_code VARCHAR(10) UNIQUE,
    created_at TIMESTAMP DEFAULT NOW()
);

Database Functions

Common database operations are wrapped in functions for consistency:

sql
-- Increment XP and handle level-ups
CREATE OR REPLACE FUNCTION increment_xp(
    user_uuid UUID,
    xp_amount INTEGER
) RETURNS TABLE(new_xp INTEGER, new_level INTEGER, leveled_up BOOLEAN) AS $$
DECLARE
    current_xp INTEGER;
    current_level INTEGER;
    new_total_xp INTEGER;
    calculated_level INTEGER;
BEGIN
    SELECT xp, level INTO current_xp, current_level
    FROM users WHERE id = user_uuid;
    
    new_total_xp := current_xp + xp_amount;
    calculated_level := calculate_level(new_total_xp);
    
    UPDATE users
    SET xp = new_total_xp, level = calculated_level
    WHERE id = user_uuid;
    
    RETURN QUERY
    SELECT new_total_xp, calculated_level, (calculated_level > current_level);
END;
$$ LANGUAGE plpgsql;

5. Authentication System

Supabase Auth Setup

BrainMapRevision uses Supabase's built-in authentication:

javascript
// assets/js/auth.js

// Sign up
async function signUp(email, password, username, role) {
    try {
        const { data, error } = await supabase.auth.signUp({
            email: email,
            password: password,
            options: {
                data: {
                    username: username,
                    role: role
                }
            }
        });
        
        if (error) throw error;
        
        // Create user profile
        await createUserProfile(data.user.id, username, role);
        
        return { success: true, user: data.user };
    } catch (error) {
        return { success: false, error: error.message };
    }
}

// Sign in
async function signIn(email, password) {
    try {
        const { data, error } = await supabase.auth.signInWithPassword({
            email: email,
            password: password
        });
        
        if (error) throw error;
        
        // Update last login
        await updateLastLogin(data.user.id);
        
        return { success: true, user: data.user };
    } catch (error) {
        return { success: false, error: error.message };
    }
}

// Sign out
async function signOut() {
    const { error } = await supabase.auth.signOut();
    if (!error) {
        window.location.href = '/index.html';
    }
}

// Get current user
async function getCurrentUser() {
    const { data: { user } } = await supabase.auth.getUser();
    return user;
}

// Check if user is authenticated
async function isAuthenticated() {
    const user = await getCurrentUser();
    return user !== null;
}

Protected Routes

Pages that require authentication should check on load:

javascript
// At the top of protected pages
document.addEventListener('DOMContentLoaded', async () => {
    const authenticated = await isAuthenticated();
    
    if (!authenticated) {
        window.location.href = '/auth/login.html';
        return;
    }
    
    // Load page content
    loadPageContent();
});

Row Level Security (RLS)

Enable RLS on all tables and create policies:

sql
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE revision_boards ENABLE ROW LEVEL SECURITY;

-- Users can only see/edit their own data
CREATE POLICY "Users can view own profile"
    ON users FOR SELECT
    USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
    ON users FOR UPDATE
    USING (auth.uid() = id);

-- Revision boards: users can CRUD their own, view public ones
CREATE POLICY "Users can create own boards"
    ON revision_boards FOR INSERT
    WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can view own and public boards"
    ON revision_boards FOR SELECT
    USING (auth.uid() = user_id OR is_public = true);

6. Implementing Features

Feature Development Workflow

  1. Create a new branch: git checkout -b feature/feature-name
  2. Create necessary HTML files in appropriate directories
  3. Add component-specific CSS in assets/css/
  4. Implement JavaScript functionality in assets/js/
  5. Update database schema if needed
  6. Test thoroughly
  7. Commit and push
  8. Create pull request

Example: Creating a Quiz Feature

Step 1: HTML Structure (pages/quizzes.html)

html
<div class="quiz-container">
    <div class="quiz-header">
        <h1>Mathematics Quiz</h1>
        <div class="quiz-progress">
            <span>Question <span id="current-question">1</span> of <span id="total-questions">10</span></span>
            <div class="progress-bar">
                <div class="progress-fill" id="progress-fill"></div>
            </div>
        </div>
    </div>
    
    <div id="quiz-content">
        <!-- Questions loaded dynamically -->
    </div>
    
    <div class="quiz-actions">
        <button id="hint-btn" class="btn btn-secondary">Use Hint (-1 token)</button>
        <button id="submit-btn" class="btn btn-primary">Submit Answer</button>
    </div>
</div>

Step 2: CSS Styling (assets/css/quiz.css)

css
.quiz-container {
    max-width: 800px;
    margin: 40px auto;
    padding: 40px;
    background: var(--card-bg);
    border-radius: 20px;
    border: 1px solid var(--border-color);
}

.quiz-progress {
    margin-top: 20px;
}

.progress-bar {
    width: 100%;
    height: 8px;
    background: var(--border-color);
    border-radius: 10px;
    overflow: hidden;
    margin-top: 10px;
}

.progress-fill {
    height: 100%;
    background: var(--accent-gradient);
    width: 10%; /* Updated via JS */
    transition: width 0.3s ease;
}

.quiz-question {
    font-size: 1.3rem;
    margin: 30px 0;
}

.quiz-options {
    display: grid;
    gap: 15px;
}

.option-btn {
    padding: 15px 20px;
    background: var(--card-bg);
    border: 2px solid var(--border-color);
    border-radius: 10px;
    cursor: pointer;
    transition: var(--transition);
}

.option-btn:hover {
    border-color: var(--accent-primary);
    background: rgba(167, 139, 250, 0.1);
}

.option-btn.selected {
    border-color: var(--accent-primary);
    background: rgba(167, 139, 250, 0.2);
}

.option-btn.correct {
    border-color: #48BB78;
    background: rgba(72, 187, 120, 0.2);
}

.option-btn.incorrect {
    border-color: #F56565;
    background: rgba(245, 101, 101, 0.2);
}

Step 3: JavaScript Logic (assets/js/quiz-engine.js)

javascript
// Quiz Engine
class QuizEngine {
    constructor(questions) {
        this.questions = questions;
        this.currentIndex = 0;
        this.score = 0;
        this.hintsUsed = 0;
        this.userAnswers = [];
    }

    getCurrentQuestion() {
        return this.questions[this.currentIndex];
    }

    submitAnswer(answerIndex) {
        const question = this.getCurrentQuestion();
        const isCorrect = answerIndex === question.correctAnswer;
        
        this.userAnswers.push({
            questionId: question.id,
            answer: answerIndex,
            correct: isCorrect
        });
        
        if (isCorrect) {
            this.score++;
        }
        
        return isCorrect;
    }

    nextQuestion() {
        this.currentIndex++;
        return this.currentIndex < this.questions.length;
    }

    getProgress() {
        return ((this.currentIndex + 1) / this.questions.length) * 100;
    }

    useHint(hintType) {
        this.hintsUsed++;
        const question = this.getCurrentQuestion();
        
        switch(hintType) {
            case 'remove_two':
                return this.removeIncorrectOptions(2);
            case 'show_topic':
                return question.topic;
            case 'first_letter':
                return question.answer[0];
            default:
                return null;
        }
    }

    removeIncorrectOptions(count) {
        const question = this.getCurrentQuestion();
        const incorrectIndexes = question.options
            .map((opt, idx) => idx)
            .filter(idx => idx !== question.correctAnswer);
        
        // Randomly select 'count' incorrect options to remove
        const toRemove = incorrectIndexes
            .sort(() => Math.random() - 0.5)
            .slice(0, count);
        
        return toRemove;
    }

    calculateXP() {
        const baseXP = 20;
        const perfectBonus = this.score === this.questions.length ? 50 : 0;
        const hintPenalty = this.hintsUsed * 2;
        
        return Math.max(baseXP + perfectBonus - hintPenalty, 10);
    }

    calculateCoins() {
        const baseCoins = 10;
        const perfectBonus = this.score === this.questions.length ? 25 : 0;
        
        return baseCoins + perfectBonus;
    }

    getResults() {
        return {
            score: this.score,
            total: this.questions.length,
            percentage: (this.score / this.questions.length) * 100,
            hintsUsed: this.hintsUsed,
            xpEarned: this.calculateXP(),
            coinsEarned: this.calculateCoins(),
            answers: this.userAnswers
        };
    }
}

// Initialize quiz
async function initQuiz() {
    const questions = await fetchQuizQuestions(subjectId, topicId);
    const quiz = new QuizEngine(questions);
    
    displayQuestion(quiz);
}

function displayQuestion(quiz) {
    const question = quiz.getCurrentQuestion();
    const container = document.getElementById('quiz-content');
    
    container.innerHTML = `
        

${question.question}

${question.options.map((opt, idx) => ` `).join('')}
`; // Update progress document.getElementById('current-question').textContent = quiz.currentIndex + 1; document.getElementById('progress-fill').style.width = quiz.getProgress() + '%'; // Add event listeners setupOptionListeners(quiz); } function setupOptionListeners(quiz) { const options = document.querySelectorAll('.option-btn'); options.forEach(btn => { btn.addEventListener('click', () => { options.forEach(o => o.classList.remove('selected')); btn.classList.add('selected'); }); }); document.getElementById('submit-btn').addEventListener('click', () => { submitQuizAnswer(quiz); }); document.getElementById('hint-btn').addEventListener('click', () => { useQuizHint(quiz); }); } async function submitQuizAnswer(quiz) { const selected = document.querySelector('.option-btn.selected'); if (!selected) { alert('Please select an answer'); return; } const answerIndex = parseInt(selected.dataset.index); const isCorrect = quiz.submitAnswer(answerIndex); // Show feedback showAnswerFeedback(selected, isCorrect); // Wait then move to next question setTimeout(() => { if (quiz.nextQuestion()) { displayQuestion(quiz); } else { showResults(quiz); } }, 2000); } function showAnswerFeedback(element, isCorrect) { element.classList.add(isCorrect ? 'correct' : 'incorrect'); const feedback = document.createElement('div'); feedback.className = 'feedback-popup'; feedback.textContent = isCorrect ? '✓ Correct!' : '✗ Incorrect'; feedback.style.color = isCorrect ? '#48BB78' : '#F56565'; element.appendChild(feedback); } async function showResults(quiz) { const results = quiz.getResults(); // Save to database await saveQuizResults(results); // Award XP and coins await awardXP(results.xpEarned); await awardCoins(results.coinsEarned); // Display results screen displayResultsScreen(results); } async function useQuizHint(quiz) { const user = await getCurrentUser(); if (user.hint_tokens < 1) { alert('No hint tokens available. Purchase more in the shop!'); return; } const hintType = 'remove_two'; // or show modal to choose const toRemove = quiz.useHint(hintType); // Update UI to disable removed options toRemove.forEach(idx => { const btn = document.querySelector(`[data-index="${idx}"]`); btn.disabled = true; btn.style.opacity = '0.3'; }); // Deduct hint token await deductHintToken(user.id); }

Step 4: Database Integration

javascript
// Fetch quiz questions from database
async function fetchQuizQuestions(subjectId, topicId, count = 10) {
    const { data, error } = await supabase
        .from('quiz_questions')
        .select('*')
        .eq('subject_id', subjectId)
        .eq('topic', topicId)
        .limit(count);
    
    if (error) {
        console.error('Error fetching questions:', error);
        return [];
    }
    
    return data;
}

// Save quiz results
async function saveQuizResults(results) {
    const user = await getCurrentUser();
    
    const { error } = await supabase
        .from('quizzes')
        .insert({
            user_id: user.id,
            subject_id: currentSubjectId,
            score: results.score,
            total_questions: results.total,
            xp_earned: results.xpEarned,
            coins_earned: results.coinsEarned,
            hints_used: results.hintsUsed
        });
    
    if (error) {
        console.error('Error saving results:', error);
    }
}

// Award XP to user
async function awardXP(amount) {
    const user = await getCurrentUser();
    
    const { data, error } = await supabase
        .rpc('increment_xp', {
            user_uuid: user.id,
            xp_amount: amount
        });
    
    if (!error && data.leveled_up) {
        showLevelUpAnimation(data.new_level);
    }
}

// Award Brain Coins
async function awardCoins(amount) {
    const user = await getCurrentUser();
    
    await supabase
        .from('users')
        .update({ 
            brain_coins: user.brain_coins + amount 
        })
        .eq('id', user.id);
}

7. Styling Guidelines

CSS Conventions

BEM Example

css
/* Block */
.quiz-card {
    background: var(--card-bg);
    border-radius: 15px;
}

/* Element */
.quiz-card__header {
    padding: 20px;
    border-bottom: 1px solid var(--border-color);
}

.quiz-card__title {
    font-size: 1.5rem;
    font-weight: 600;
}

/* Modifier */
.quiz-card--completed {
    opacity: 0.6;
}

.quiz-card--highlighted {
    border: 2px solid var(--accent-primary);
}

Responsive Design

css
/* Mobile first */
.feature-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 20px;
}

/* Tablet */
@media (min-width: 768px) {
    .feature-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 30px;
    }
}

/* Desktop */
@media (min-width: 1024px) {
    .feature-grid {
        grid-template-columns: repeat(4, 1fr);
        gap: 40px;
    }
}

Dark Mode Support

Always test features in both light and dark modes. Use CSS variables:

css
/* GOOD: Uses theme variables */
.card {
    background: var(--card-bg);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
}

/* BAD: Hardcoded colors */
.card {
    background: white;
    color: #333;
    border: 1px solid #ddd;
}

Accessibility

html
<button 
    class="btn btn-primary" 
    aria-label="Start quiz"
    aria-describedby="quiz-description">
    Start Quiz
</button>

<p id="quiz-description" class="sr-only">
    Begin a 10 question mathematics quiz
</p>

8. Testing

Manual Testing Checklist

Before submitting a PR, ensure you've tested:

Browser Testing

Test on:

Testing Database Functions

javascript
// Test in browser console
async function testXPFunction() {
    const user = await getCurrentUser();
    console.log('Current XP:', user.xp);
    
    const result = await supabase
        .rpc('increment_xp', {
            user_uuid: user.id,
            xp_amount: 100
        });
    
    console.log('Result:', result.data);
}

testXPFunction();

Error Handling

Always handle errors gracefully:

javascript
async function loadQuizData() {
    try {
        const questions = await fetchQuizQuestions(subjectId);
        
        if (!questions || questions.length === 0) {
            showError('No questions available for this topic');
            return;
        }
        
        initQuiz(questions);
        
    } catch (error) {
        console.error('Error loading quiz:', error);
        showError('Failed to load quiz. Please try again later.');
    }
}

function showError(message) {
    const errorDiv = document.createElement('div');
    errorDiv.className = 'error-message';
    errorDiv.textContent = message;
    document.body.appendChild(errorDiv);
    
    setTimeout(() => errorDiv.remove(), 5000);
}

9. Deployment

GitHub Pages Deployment

The site is hosted on GitHub Pages and automatically deploys from the main branch.

  1. Push changes to main branch (or merge PR)
  2. GitHub Actions builds and deploys automatically
  3. Site updates at: https://jayden4400338.github.io/BrainMapRevision

Environment Variables

For production, use environment variables for sensitive data:

⚠️ Security Warning

Never commit API keys or secrets to the repository. Use environment variables or GitHub Secrets for sensitive configuration.

Database Migrations

When updating the database schema:

  1. Create migration file in supabase/migrations/
  2. Name it with timestamp: 20250121_add_achievements_table.sql
  3. Run migration in Supabase dashboard or CLI
  4. Test thoroughly before deploying to production

Pre-deployment Checklist

10. Contributing Guidelines

Code of Conduct

Please read and follow our Code of Conduct. We're committed to providing a welcoming and inclusive environment.

Contribution Workflow

  1. Find an issue - Check existing issues or create one
  2. Fork & Clone - Fork the repo and clone to your machine
  3. Create branch - git checkout -b feature/your-feature
  4. Make changes - Implement your feature/fix
  5. Test - Thoroughly test your changes
  6. Commit - Use meaningful commit messages
  7. Push - Push to your fork
  8. Pull Request - Create PR with description

Commit Message Format

plaintext
Type: Brief description (max 50 chars)

Detailed explanation of changes if needed.
- What was changed
- Why it was changed
- Any breaking changes

Closes #issue-number

Types:

Pull Request Guidelines

Your PR should:

What to Contribute

Content:

Code:

Documentation:

Getting Help

If you need help:

🎉 Thank You!

Every contribution, no matter how small, helps make BrainMapRevision better for students everywhere. We appreciate your time and effort!

Back to Top