Sprint Calendar Sync and Preferences Backend - Technical Documentation
Table of Contents
- Project Overview
- Sprint Calendar Sync Feature
- Delete from Calendar Feature
- Admin Calendar Event Creation
- Preferences Backend Integration
- Technical Architecture
- API Endpoints
- Key Code Components
- 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:
- Logged-in users: Preferences stored in backend database
- 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
- Build Test: Run
maketo ensure Jekyll builds successfully - Date Verification: Check that displayed dates match school calendar
- API Test: Verify events appear in user’s calendar after sync
- 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
- 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
- Calendar Event Deletion
- Mass delete functionality for synced events
- Confirmation dialog to prevent accidental deletion
- Bulk delete API integration
- 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
- 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
- Sprint weeks = Calendar weeks: Sprint week numbers directly correspond to calendar week numbers (no remapping needed)
- JSON embedding: Used
<script type="application/json">to embed calendar data, avoiding Liquid lint issues - Bulk API first: Try bulk endpoints first, fall back to individual requests if unavailable
- SASS-only styling: Maintain all styles in
.scssfiles for consistency and maintainability