Ahaan

Sprint Calendar Sync and Preferences Backend

Technical documentation for the Sprint Calendar Sync feature and Preferences Backend integration in the Nightmare course platform.

Sprint Calendar Sync and Preferences Backend - Technical Documentation


Table of Contents

  1. Project Overview
  2. Sprint Calendar Sync Feature
  3. Delete from Calendar Feature
  4. Admin Calendar Event Creation
  5. Preferences Backend Integration
  6. Technical Architecture
  7. API Endpoints
  8. Key Code Components
  9. Testing and Validation

Project Overview

This documentation covers the development of two major features for Open Coding Society Website

Sprint Calendar Sync

A feature that allows students to sync their course sprint schedules directly to their personal calendar, with intelligent handling of school breaks and holidays.

Preferences Backend

Integration with a Spring Boot backend to persist user preferences (theme, colors, fonts, accessibility settings) across sessions and devices.

Technologies Used

  • Jekyll - Static site generator
  • Liquid - Templating language
  • JavaScript (ES6+) - Frontend logic
  • SASS/SCSS - Styling
  • Spring Boot (Java) - Backend API
  • YAML - Configuration data

Sprint Calendar Sync Feature

Problem Statement

Students needed a way to:

  • Automatically add course events to their personal calendars
  • Have events properly dated according to the school calendar
  • Skip break weeks (Winter Break, Spring Break, etc.)
  • Handle holiday adjustments (e.g., Labor Day moving materials to Tuesday)

Solution Architecture

+------------------+     +-------------------+     +------------------+
|  School          |---->|  Sprint Layout    |---->|  Calendar API    |
|  Calendar YAML   |     |  (sprint.html)    |     |  (Backend)       |
+------------------+     +-------------------+     +------------------+
        |                       |                       |
        v                       v                       v
   Week dates            JavaScript            User's personal
   and holidays          functions              calendar

Key Innovation: Skip Week Handling

The school calendar YAML file defines which weeks are breaks:

# Example from school_calendar.yml
school_calendar_example = """
weeks:
  18:
    monday: "2025-12-22"
    friday: "2025-12-26"
    holidays: ["Winter Break"]
    skip_week: true  # Key property for skipping
  19:
    monday: "2025-12-29"
    friday: "2026-01-02"
    holidays: ["Winter Break"]
    skip_week: true
  26:
    monday: "2026-02-16"
    friday: "2026-02-20"
    holidays: ["Presidents' Day Break"]
    skip_week: true
  33:
    monday: "2026-04-06"
    friday: "2026-04-10"
    holidays: ["Spring Break"]
    skip_week: true
"""
print(school_calendar_example)

JavaScript Implementation

The calendar data is embedded into the page via Liquid templating and parsed by JavaScript.

Converting YAML to JavaScript Object

# This Liquid template converts YAML to JSON for JavaScript
liquid_template = """
<script id="school-calendar-json" type="application/json">
{
  "schoolYear": "",
  "firstDay": "",
  "lastDay": "",
  "weeks": {
    
  }
}
</script>
"""
print(liquid_template)

Core Date Functions

These JavaScript functions handle the date logic for calendar sync:

# JavaScript function: getReadingDate
# Returns the Monday (or Tuesday if holiday) for a given week number

get_reading_date_js = """
function getReadingDate(weekNum) {
    const week = SCHOOL_CALENDAR.weeks[weekNum];
    if (!week) return null;
    
    // Skip break weeks - no reading materials during breaks
    if (week.skipWeek) return null;
    
    // If there's a Monday holiday, use Tuesday instead
    if (week.holidays && week.holidays.length > 0 && week.tuesday) {
        return week.tuesday;
    }
    return week.monday;
}
"""
print(get_reading_date_js)
# JavaScript function: getAssessmentDate
# Returns the Friday for summative assessments

get_assessment_date_js = """
function getAssessmentDate(weekNum) {
    const week = SCHOOL_CALENDAR.weeks[weekNum];
    if (!week) return null;
    
    // Skip break weeks - no assessments during breaks
    if (week.skipWeek) return null;
    
    return week.friday;
}
"""
print(get_assessment_date_js)
# JavaScript function: getSprintDateRange
# Gets the start and end dates for a sprint

get_sprint_date_range_js = """
function getSprintDateRange(startWeek, endWeek) {
    const startWeekData = SCHOOL_CALENDAR.weeks[startWeek];
    const endWeekData = SCHOOL_CALENDAR.weeks[endWeek];
    
    if (!startWeekData || !endWeekData) {
        return { start: null, end: null };
    }
    
    return {
        start: startWeekData.monday,
        end: endWeekData.friday
    };
}
"""
print(get_sprint_date_range_js)

Main Sync Function

The syncSprintToCalendar function orchestrates the entire sync process:

# Main sync function (simplified overview)
sync_sprint_overview = """
async function syncSprintToCalendar(sprintKey, course, startWeek, endWeek) {
    // 1. Get sync options from checkboxes
    const syncFormative = document.querySelector(`.sync-reading-materials[data-sprint="${sprintKey}"]`)?.checked;
    const syncSummative = document.querySelector(`.sync-assessments[data-sprint="${sprintKey}"]`)?.checked;
    
    // 2. Build events array
    const events = [];
    
    // 3. Loop through each week in the sprint
    for (const weekCard of weekCards) {
        const weekNum = parseInt(weekCard.dataset.week);
        
        // Get dates (returns null for skip weeks)
        const readingDate = getReadingDate(weekNum);
        const assessmentDate = getAssessmentDate(weekNum);
        
        // Create Formative event (Monday)
        if (syncFormative && readingDate) {
            events.push({
                title: `Week ${weekNum} Formative - ${courseName}`,
                description: `Reading Materials for the week`,
                date: readingDate,
                period: courseName
            });
        }
        
        // Create Summative event (Friday)
        if (syncSummative && assessmentDate) {
            events.push({
                title: `Week ${weekNum} Summative - ${courseName}`,
                description: `Assessments Due`,
                date: assessmentDate,
                period: courseName
            });
        }
    }
    
    // 4. Send bulk request to API
    const response = await fetch(`${javaURI}/api/calendar/add_events`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ events: events })
    });
    
    // 5. Handle response
    const result = await response.json();
    // API returns: { created: X, updated: Y, failed: Z }
}
"""
print(sync_sprint_overview)

UI Components

The sync feature includes a dropdown UI with:

  • Date range preview
  • Break week warnings
  • Sync options (Formative/Summative checkboxes)
  • Sync and Delete buttons
<div class="sprint-date-dropdown">
    <div class="calendar-sync-info">
        <div class="sprint-week-range">Weeks 34-37</div>
        <div class="sprint-date-preview">
            <div class="date-range-display">
                <span class="date-label">Start:</span>
                <span class="date-value">Apr 13, 2026</span>
            </div>
            <div class="date-range-display">
                <span class="date-label">End:</span>
                <span class="date-value">May 8, 2026</span>
            </div>
        </div>
    </div>
    <div class="sync-options">
        <label><input type="checkbox" checked> Formative (Monday)</label>
        <label><input type="checkbox" checked> Summative (Friday)</label>
    </div>
    <div class="date-actions">
        <button class="sync-sprint-calendar-btn">Sync to Calendar</button>
        <button class="delete-sprint-calendar-btn">Remove from Calendar</button>
    </div>
</div>

Delete from Calendar Feature

Problem Statement

After syncing events, users needed a way to bulk remove all events for a sprint without manually deleting each one.

Implementation

# Delete function implementation
delete_function_js = """
async function deleteSprintFromCalendar(sprintKey, course, startWeek, endWeek) {
    // 1. Confirm with user before deleting
    const confirmMessage = `Are you sure you want to remove all ${courseName} events 
                           for ${sprintKey} (Weeks ${startWeek}-${endWeek})?`;
    if (!confirm(confirmMessage)) return;
    
    // 2. Build list of event titles to delete (must match synced titles exactly)
    const eventTitlesToDelete = [];
    for (const weekCard of weekCards) {
        const weekNum = parseInt(weekCard.dataset.week);
        
        // Match the title patterns used during sync
        eventTitlesToDelete.push(`Week ${weekNum} Formative - ${courseName}`);
        eventTitlesToDelete.push(`Week ${weekNum} Summative - ${courseName}`);
    }
    
    // 3. Try bulk delete endpoint
    const response = await fetch(`${javaURI}/api/calendar/delete_events`, {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ titles: eventTitlesToDelete })
    });
    
    // 4. Handle response
    const result = await response.json();
    // API returns: { deleted: X }
}
"""
print(delete_function_js)

SASS Styling

All styles are kept in SASS files (no inline CSS):

# SASS styles for delete button (timeline.scss)
delete_button_scss = """
.delete-sprint-calendar-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    background: #dc2626;  // Red danger color
    color: white;
    border: none;
    border-radius: 8px;
    font-weight: 600;
    font-size: 0.875rem;
    cursor: pointer;
    transition: all 0.2s ease;
    width: 100%;
    
    &:hover:not(:disabled) {
        background: #b91c1c;  // Darker red on hover
        transform: translateY(-1px);
    }
    
    &:disabled {
        opacity: 0.6;
        cursor: not-allowed;
    }
    
    i {
        font-size: 0.875rem;
    }
}

.date-actions {
    display: flex;
    flex-direction: column;  // Stack buttons vertically
    gap: 0.5rem;
    margin-top: 1rem;
}
"""
print(delete_button_scss)

Admin Calendar Event Creation

Problem Statement

Instructors and administrators needed a way to:

  • Manually create custom calendar events (checkpoints, special assessments, etc.)
  • Add events that don’t follow the standard weekly pattern
  • Have admin-only controls that regular students cannot access

Solution: Role-Based Admin Section

The calendar sync dropdown now includes an admin-only section that appears only for users with the “Admin” role. This section allows admins to create custom events with:

  • Custom title
  • Event type (Summative, Formative, Checkpoint, Custom)
  • Specific date selection
  • Optional description

Admin Detection Logic

# Admin check and show admin section
admin_check_js = """
async function checkAndShowAdminSection() {
    try {
        const configModule = await import('/assets/js/api/config.js');
        const javaURI = configModule.javaURI;
        
        // Try to get user info from the backend
        const response = await fetch(`${javaURI}/api/person/get`, {
            method: 'GET',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include'
        });
        
        if (!response.ok) {
            console.log('User not logged in or unable to fetch user info');
            return;
        }
        
        const user = await response.json();
        const isAdmin = user.role === 'Admin' || user.role === 'ADMIN';
        
        if (isAdmin) {
            console.log('Admin user detected, showing admin calendar controls');
            // Show all admin calendar sections
            document.querySelectorAll('.admin-calendar-section').forEach(section => {
                section.classList.remove('hidden');
            });
            
            // Set default date to today
            const today = new Date().toISOString().split('T')[0];
            document.querySelectorAll('.admin-event-date').forEach(input => {
                if (!input.value) {
                    input.value = today;
                }
            });
        }
    } catch (error) {
        console.log('Could not check admin status:', error.message);
    }
}
"""
print(admin_check_js)

Create Admin Event Function

# Create a custom calendar event (admin only)
create_admin_event_js = """
async function createAdminCalendarEvent(sprintKey, course) {
    const statusEl = document.querySelector(`.admin-event-status[data-sprint="${sprintKey}"]`);
    const btn = document.querySelector(`.admin-create-event-btn[data-sprint="${sprintKey}"]`);
    
    // Get form values
    const titleInput = document.querySelector(`.admin-event-title[data-sprint="${sprintKey}"]`);
    const typeSelect = document.querySelector(`.admin-event-type[data-sprint="${sprintKey}"]`);
    const dateInput = document.querySelector(`.admin-event-date[data-sprint="${sprintKey}"]`);
    const descriptionInput = document.querySelector(`.admin-event-description[data-sprint="${sprintKey}"]`);
    
    const title = titleInput?.value?.trim();
    const eventType = typeSelect?.value || 'summative';
    const eventDate = dateInput?.value;
    const description = descriptionInput?.value?.trim() || '';
    
    // Validation
    if (!title) {
        showAdminStatus(statusEl, 'Please enter an event title', 'error');
        return;
    }
    
    if (!eventDate) {
        showAdminStatus(statusEl, 'Please select a date', 'error');
        return;
    }
    
    btn.disabled = true;
    showAdminStatus(statusEl, 'Creating event...', 'loading');
    
    try {
        const courseName = course.toUpperCase();
        
        // Build event title with type prefix
        let fullTitle = title;
        switch (eventType) {
            case 'summative':
                fullTitle = `[Summative] ${title} - ${courseName}`;
                break;
            case 'formative':
                fullTitle = `[Formative] ${title} - ${courseName}`;
                break;
            case 'checkpoint':
                fullTitle = `[Checkpoint] ${title} - ${courseName}`;
                break;
            default:
                fullTitle = `${title} - ${courseName}`;
        }
        
        const event = {
            title: fullTitle,
            description: description || `${eventType} event for ${courseName}`,
            date: eventDate,
            period: courseName
        };
        
        // Send to calendar API
        const response = await fetch(`${javaURI}/api/calendar/add_event`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include',
            body: JSON.stringify(event)
        });
        
        if (response.ok) {
            showAdminStatus(statusEl, 'Event created successfully!', 'success');
            titleInput.value = '';
            descriptionInput.value = '';
        } else {
            showAdminStatus(statusEl, `Failed to create event`, 'error');
        }
    } catch (error) {
        showAdminStatus(statusEl, 'Error creating event', 'error');
    } finally {
        btn.disabled = false;
    }
}
"""
print(create_admin_event_js)

Admin UI HTML Structure

The admin section is hidden by default and only shown when checkAndShowAdminSection() detects an admin user:

<!-- Admin Only: Custom Summative Creation -->
<div class="admin-calendar-section hidden" data-sprint="SprintX">
    <div class="admin-section-header">
        <i class="fas fa-user-shield"></i>
        <span>Admin: Create Custom Event</span>
    </div>
    <div class="admin-event-form">
        <div class="admin-form-group">
            <label>Event Title</label>
            <input type="text" class="admin-event-title" placeholder="e.g., Midterm Review">
        </div>
        <div class="admin-form-group">
            <label>Event Type</label>
            <select class="admin-event-type">
                <option value="summative">Summative (Assessment)</option>
                <option value="formative">Formative (Lesson/Reading)</option>
                <option value="checkpoint">Checkpoint</option>
                <option value="custom">Custom</option>
            </select>
        </div>
        <div class="admin-form-group">
            <label>Date</label>
            <input type="date" class="admin-event-date">
        </div>
        <div class="admin-form-group">
            <label>Description (optional)</label>
            <textarea class="admin-event-description" placeholder="Additional details..." rows="2"></textarea>
        </div>
        <div class="admin-form-actions">
            <button class="admin-create-event-btn">
                <i class="fas fa-plus"></i> Create Event
            </button>
        </div>
        <p class="admin-event-status"></p>
    </div>
</div>

Admin Section SASS Styling

The admin section has a distinct purple theme to visually differentiate it from regular user controls:

# Admin section SASS styles (timeline.scss)
admin_section_scss = """
// Admin Calendar Section
.admin-calendar-section {
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px dashed rgba(255, 255, 255, 0.15);
    
    &.hidden {
        display: none;
    }
}

.admin-section-header {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    padding: 8px 12px;
    background: rgba(168, 85, 247, 0.15);  // Purple tint
    border-radius: 6px;
    border-left: 3px solid #a855f7;
    
    i {
        color: #a855f7;
    }
    
    span {
        font-weight: 600;
        color: #c084fc;
    }
}

.admin-create-event-btn {
    width: 100%;
    padding: 12px 16px;
    background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%);
    border: none;
    border-radius: 8px;
    color: white;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    
    &:hover {
        opacity: 0.9;
        box-shadow: 0 4px 12px rgba(168, 85, 247, 0.3);
    }
    
    &:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }
}
"""
print(admin_section_scss)

Backend Requirements for Admin Calendar Events

The admin calendar event creation feature requires a new backend endpoint that allows administrators to push calendar events to ALL students in a course or section, rather than just the admin’s personal calendar.

Required API Endpoint

  • POST /api/calendar/admin/add_event
  • Authorization: Admin role required
  • Purpose: Create calendar events for all users in a specified period/course
# Expected API Request/Response Schema

admin_event_api = {
    "endpoint": "POST /api/calendar/admin/add_event",
    "request_body": {
        "title": "Summative Assignment - Sprint 3",  # Event title
        "description": "Final project submission",    # Event description
        "date": "2025-02-14",                        # Event date (YYYY-MM-DD)
        "type": "summative",                         # Event type: summative, checkpoint
        "periodId": 1,                               # Target period/course ID
        "color": "#ef4444"                           # Optional color override
    },
    "response": {
        "success": True,
        "message": "Event created for 32 students",
        "eventsCreated": 32,
        "eventDetails": {
            "id": "admin-event-uuid",
            "title": "Summative Assignment - Sprint 3",
            "date": "2025-02-14"
        }
    }
}

print("Admin Event API Schema:")
import json
print(json.dumps(admin_event_api, indent=2))

Preferences Backend Integration

Overview

User preferences (theme, colors, fonts, TTS settings) are now persisted to a Spring Boot backend, allowing preferences to sync across devices.

Data Model Transformation

Frontend and backend use different property naming conventions:

# Data format conversion functions
data_conversion = """
// Frontend format -> Backend format
function toBackendFormat(prefs) {
    return {
        backgroundColor: prefs.bg,
        textColor: prefs.text,
        fontFamily: prefs.font,
        fontSize: prefs.size,
        accentColor: prefs.accent,
        selectionColor: prefs.selectionColor || '#3b82f6',
        buttonStyle: prefs.buttonStyle || 'rounded',
        language: prefs.language || '',
        ttsVoice: prefs.ttsVoice || '',
        ttsRate: prefs.ttsRate || 1.0,
        ttsPitch: prefs.ttsPitch || 1.0,
        ttsVolume: prefs.ttsVolume || 1.0,
        customThemes: JSON.stringify(prefs.customThemes || {})
    };
}

// Backend format -> Frontend format
function toFrontendFormat(backendPrefs) {
    return {
        bg: backendPrefs.backgroundColor,
        text: backendPrefs.textColor,
        font: backendPrefs.fontFamily,
        size: backendPrefs.fontSize,
        accent: backendPrefs.accentColor,
        selectionColor: backendPrefs.selectionColor,
        buttonStyle: backendPrefs.buttonStyle,
        // ... TTS settings
        customThemes: JSON.parse(backendPrefs.customThemes || '{}')
    };
}
"""
print(data_conversion)

API Integration

# Fetch preferences from backend
fetch_preferences = """
async function fetchPreferencesFromBackend() {
    try {
        const res = await fetch(`${javaURI}/api/user/preferences`, {
            ...fetchOptions,
            method: 'GET'
        });
        
        if (res.status === 401) {
            // Not logged in - use localStorage fallback
            isLoggedIn = false;
            return null;
        }
        
        if (res.ok) {
            isLoggedIn = true;
            const data = await res.json();
            if (data && data.id) {
                backendPrefsExist = true;
                return toFrontendFormat(data);
            }
        }
        return null;
    } catch (e) {
        console.error('fetchPreferencesFromBackend error', e);
        return null;
    }
}
"""
print(fetch_preferences)
# Save preferences to backend
save_preferences = """
async function savePreferencesToBackend(prefs) {
    try {
        const backendData = toBackendFormat(prefs);
        
        // Use POST for new, PUT for update
        const method = backendPrefsExist ? 'PUT' : 'POST';
        
        const res = await fetch(`${javaURI}/api/user/preferences`, {
            ...fetchOptions,
            method: method,
            body: JSON.stringify(backendData)
        });
        
        if (res.ok) {
            backendPrefsExist = true;
            return true;
        }
        return false;
    } catch (e) {
        console.error('savePreferencesToBackend error', e);
        return false;
    }
}
"""
print(save_preferences)

Fallback Strategy

The preferences system uses a hybrid approach:

  1. Logged-in users: Preferences stored in backend database
  2. Guest users: Preferences stored in localStorage
async function loadPreferences() {
    // Try backend first
    const backendPrefs = await fetchPreferencesFromBackend();
    if (backendPrefs) {
        applyPreferences(backendPrefs);
        return;
    }
    
    // Fallback to localStorage
    const localPrefs = JSON.parse(localStorage.getItem('sitePreferences'));
    if (localPrefs) {
        applyPreferences(localPrefs);
    }
}

Technical Architecture

File Structure

nightmare/
├── _data/
│   └── school_calendar.yml      # School calendar with break weeks
├── _layouts/
│   ├── sprint.html              # Sprint timeline with calendar sync
│   └── dashboard.html           # Preferences UI
├── _sass/
│   └── open-coding/
│       └── timeline.scss        # Timeline and button styles
├── assets/
│   └── js/
│       ├── user-preferences.js  # Global preferences handling
│       └── api/
│           └── config.js        # API endpoint configuration

Data Flow Diagram

+----------------------------------------------------------------------+
|                          CALENDAR SYNC FLOW                          |
+----------------------------------------------------------------------+

User clicks "Sync to Calendar"
            |
            v
+---------------------+
|  Get sync options   |  (checkboxes for Formative/Summative)
+---------------------+
            |
            v
+---------------------+
|  Loop through weeks |
|  in sprint          |
+---------------------+
            |
            v
+---------------------+      +------------------+
|  For each week:     |----->|  SCHOOL_CALENDAR |
|  - getReadingDate() |      |  JavaScript      |
|  - getAssessDate()  |      |  object          |
+---------------------+      +------------------+
            |
            | (skipWeek: true returns null, skips week)
            v
+---------------------+
|  Build events array |
|  with dates & info  |
+---------------------+
            |
            v
+---------------------+      +------------------+
|  POST /api/calendar |----->|  Backend API     |
|  /add_events        |      |  (Spring Boot)   |
+---------------------+      +------------------+
            |
            v
+---------------------+
|  Show success/error |
|  status message     |
+---------------------+

API Endpoints

Calendar API Endpoints

Endpoint Method Description Request Body Response
/api/calendar/add_events POST Bulk add events { events: [...] } { created: N, updated: N, failed: N }
/api/calendar/add_event POST Add single event Event object Success/failure
/api/calendar/delete_events DELETE Bulk delete by titles { titles: [...] } { deleted: N }
/api/calendar/delete_event DELETE Delete single event { title: "..." } Success/failure

Preferences API Endpoints

Endpoint Method Description Request Body Response
/api/user/preferences GET Get user preferences - Preferences object
/api/user/preferences POST Create preferences Preferences object Created preferences
/api/user/preferences PUT Update preferences Preferences object Updated preferences
/api/user/preferences DELETE Delete preferences - Success/failure

Key Code Components

School Calendar YAML Structure

# Complete example of school_calendar.yml structure
school_calendar_structure = """
# School Calendar 2025-2026
school_year: "2025-2026"
first_day: "2025-08-13"
last_day: "2026-06-04"

weeks:
  # Regular school week
  0:
    monday: "2025-08-18"
    friday: "2025-08-22"
    theme: "Onboarding Week 1"
  
  # Week with Monday holiday (materials on Tuesday)
  2:
    monday: "2025-09-01"
    friday: "2025-09-05"
    holidays: ["Labor Day"]
    holiday_adjustment: "tuesday"
    tuesday: "2025-09-02"  # Materials assigned here instead
  
  # Break week (skipped entirely)
  18:
    monday: "2025-12-22"
    friday: "2025-12-26"
    holidays: ["Winter Break"]
    skip_week: true  # No events created for this week
"""
print(school_calendar_structure)

Break Weeks in Current Calendar

Week Number Dates Holiday Skip
14 Nov 24-28, 2025 Thanksgiving Yes
18 Dec 22-26, 2025 Winter Break Yes
19 Dec 29 - Jan 2 Winter Break Yes
26 Feb 16-20, 2026 Presidents Day Break Yes
33 Apr 6-10, 2026 Spring Break Yes

Preferences Theme Presets

# Available theme presets
presets = {
    'Midnight': {
        'bg': '#0b1220',
        'text': '#e6eef8',
        'accent': '#3b82f6'
    },
    'Light': {
        'bg': '#ffffff',
        'text': '#0f172a',
        'accent': '#2563eb'
    },
    'Green': {
        'bg': '#154734',
        'text': '#e6f6ef',
        'accent': '#10b981'
    },
    'Sepia': {
        'bg': '#f4ecd8',
        'text': '#3b2f2f',
        'accent': '#b45309'
    },
    'Cyberpunk': {
        'bg': '#0a0a0f',
        'text': '#f0f0f0',
        'accent': '#f72585'
    },
    'Ocean': {
        'bg': '#0c1929',
        'text': '#e0f2fe',
        'accent': '#06b6d4'
    }
}

for name, colors in presets.items():
    print(f"{name}: BG={colors['bg']}, Text={colors['text']}, Accent={colors['accent']}")

Testing and Validation

Test Cases for Calendar Sync

Test Case Input Expected Result
Regular week sync Week 5 Formative (Mon Sep 22) + Summative (Fri Sep 26)
Holiday week sync Week 2 (Labor Day) Formative on Tuesday (Sep 2)
Break week sync Week 18 (Winter) No events created (skipped)
Sprint 9 (Weeks 34-37) Contains skip week 33 8 events (4 weeks x 2)

Validation Steps

  1. Build Test: Run make to ensure Jekyll builds successfully
  2. Date Verification: Check that displayed dates match school calendar
  3. API Test: Verify events appear in user’s calendar after sync
  4. Delete Test: Confirm bulk delete removes all sprint events
# Test case: Verify Sprint 9 date calculations
def test_sprint_9_dates():
    """Sprint 9 covers weeks 34-37"""
    # Expected dates based on school_calendar.yml
    expected = {
        34: {'monday': '2026-04-13', 'friday': '2026-04-17'},
        35: {'monday': '2026-04-20', 'friday': '2026-04-24'},
        36: {'monday': '2026-04-27', 'friday': '2026-05-01'},
        37: {'monday': '2026-05-04', 'friday': '2026-05-08'}
    }
    
    # Week 33 should be skipped (Spring Break)
    print("Sprint 9 Date Verification:")
    print("Week 33 (Spring Break): SKIPPED")
    for week, dates in expected.items():
        print(f"Week {week}: Formative={dates['monday']}, Summative={dates['friday']}")
    
    # Sprint date range
    print(f"\nSprint 9 Range: {expected[34]['monday']} to {expected[37]['friday']}")
    print("Expected: Start: Apr 13, 2026 | End: May 8, 2026")

test_sprint_9_dates()

Summary of Accomplishments

Features Implemented

  1. Sprint Calendar Sync
    • Automated syncing of course events to personal calendar
    • Intelligent skip week handling for school breaks
    • Holiday adjustment (Monday holidays move to Tuesday materials)
    • Bulk API requests for efficient syncing
  2. Calendar Event Deletion
    • Mass delete functionality for synced events
    • Confirmation dialog to prevent accidental deletion
    • Bulk delete API integration
  3. Preferences Backend Integration
    • RESTful API integration with Spring Boot backend
    • Data format conversion (frontend to backend)
    • Hybrid storage (backend for logged-in, localStorage for guests)
    • CRUD operations for user preferences
  4. Code Quality
    • All CSS moved to SASS files (no inline styles)
    • Proper error handling and user feedback
    • Fallback mechanisms for API failures

Key Technical Decisions

  1. Sprint weeks = Calendar weeks: Sprint week numbers directly correspond to calendar week numbers (no remapping needed)
  2. JSON embedding: Used <script type="application/json"> to embed calendar data, avoiding Liquid lint issues
  3. Bulk API first: Try bulk endpoints first, fall back to individual requests if unavailable
  4. SASS-only styling: Maintain all styles in .scss files for consistency and maintainability
Scroll to top