Troubleshooting Guide

Common issues, debugging steps, and solutions for class management problems. This guide covers authentication issues, permission errors, content access problems, and ownership transfer difficulties.

Quick Diagnostic Checklist

Before filing a bug report, check these common issues:
  • ✅ Are you logged in with the correct account?
  • ✅ Does your account have educator or admin role?
  • ✅ Have you refreshed the page after making changes?
  • ✅ Are you the current owner or a content expert for the class?
  • ✅ Is your browser allowing cookies (required for Auth v2.0)?
  • ✅ Check browser console for JavaScript errors (F12)

Authentication & Access Issues

Problem: 401 Unauthorized Errors

Symptoms: API requests return 401, "Unauthorized" error messages

Possible Causes & Solutions

Not Logged In

Solution: Navigate to login page and authenticate with your credentials.

Browser Console Check
// Check if user is authenticated
const user = await UALS_Auth.getUser();
console.log('Current user:', user);

// If null, redirect to login
if (!user) {
  window.location.href = '/login.html';
}

Session Expired

Solution: Log out and log back in to refresh your session.

Cookies Blocked

Solution: UALS Auth v2.0 requires httpOnly cookies. Enable cookies in your browser settings.

Chrome: Settings → Privacy and security → Cookies → Allow all cookies (or exception for uals.app)

Firefox: Settings → Privacy & Security → Standard or Custom (allow cookies)

Missing credentials: 'include' in fetch()

Solution: If you're a developer, ensure all API calls include credentials: 'include'.

// ✅ CORRECT
fetch('/api/school/classes', {
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' }
});

// ❌ WRONG - missing credentials
fetch('/api/school/classes', {
  headers: { 'Content-Type': 'application/json' }
});

Problem: 403 Forbidden - Access Denied

Symptoms: "Access denied", "You do not have permission" error messages

Common Scenarios

Scenario 1: Content Expert Can't Edit After Ownership Transfer

Root Cause: Content expert invitations are cleared when ownership transfers.

Solution: New owner must re-invite the expert.

// Steps for new owner:
1. Open class details modal
2. Click "Show Content Experts"
3. Click "Add Content Expert"
4. Enter expert's email
5. Submit invitation

Scenario 2: Original Creator Can't Edit After Transfer

Root Cause: Creator role is for audit trail only, not permissions.

Solution: Original creator must be re-invited as content expert by new owner.

Scenario 3: Email Mismatch

Root Cause: User logged in with different email than invitation.

Solution: Verify login email matches invited email exactly (case-sensitive).

// Check current login email
const user = await UALS_Auth.getUser();
console.log('Logged in as:', user.email);

// Compare with class data
const classData = await fetch('/api/school/classes', {
  credentials: 'include'
}).then(r => r.json());

const myClass = classData.classes.find(c => c.id === 'class-xxx');
console.log('Owner:', myClass.teacherEmail);
console.log('Experts:', myClass.contentExperts);

Scenario 4: Wrong Role

Root Cause: User account is not educator or admin.

Solution: Contact system administrator to update account role.

// Check user role
const user = await UALS_Auth.getUser();
console.log('User role:', user.role);

// Required roles for class management:
// - 'educator'
// - 'admin'

Content Editing Issues

Problem: Can't Access Content Editor

Symptoms: 403 error when clicking "Edit Content", redirected away from content editor

Debugging Steps

Check User Email

// In browser console:
const user = await UALS_Auth.getUser();
console.log('My email:', user.email);

Check Class Ownership & Experts

// Fetch class data
const classData = await fetch(`/api/school/classes`, {
  credentials: 'include'
}).then(r => r.json());

const myClass = classData.classes.find(c => c.id === 'class-xxx');

console.log('Owner:', myClass.teacherEmail);
console.log('Creator:', myClass.creatorEmail);
console.log('Experts:', myClass.contentExperts);

// Your email must be either:
// - myClass.teacherEmail (owner), OR
// - in myClass.contentExperts array

Check Backend Permission Function

If you're a developer debugging server-side issues, check the canEditClassContent() function in /src/routes/classContentCache.routes.js.

Common Bug: Function checks creatorEmail instead of teacherEmail. Should be:
const classOwnerEmail = classData.teacherEmail;
if (classOwnerEmail && userEmail === classOwnerEmail) {
  return true;
}

Problem: Content Edits Not Saving

Symptoms: Changes disappear after page refresh, "Save failed" messages

Possible Causes & Solutions

  1. GCS Connection Issue
    • Check CONTENT_CACHE_METHOD=GCS in environment variables
    • Verify GCS bucket name and credentials
    • Check GCS bucket permissions (writer access required)
  2. Network Timeout
    • Large content may timeout - break into smaller chunks
    • Check browser Network tab (F12) for failed requests
  3. Version Limit Reached
    • Maximum 4 versions per hash - oldest is auto-deleted
    • This is expected behavior, not an error

Problem: Can't See Other Expert's Edits

Symptoms: Content expert A's edits not visible to expert B

Solutions

  1. Check Active Version

    Expert A may have created a new version without setting it as active. Owner or another expert should set the correct version as active.

  2. Refresh Content Cache

    Hard refresh the page (Ctrl+Shift+R on Windows, Cmd+Shift+R on Mac)

  3. Different Content Hash

    If config changed, edits may be on a different hash. Check metadata.json in GCS to see which hash was edited.

Ownership Transfer Issues

Problem: Can't Initiate Transfer

Symptoms: "Transfer Ownership" button disabled or returns error

Common Causes

  1. Not Current Owner
    • Only the current owner (teacherEmail) can initiate transfers
    • Original creator (creatorEmail) cannot transfer unless also current owner
  2. Pending Transfer Exists
    • Only one pending transfer allowed at a time
    • Solution: Cancel existing transfer first, then initiate new one
  3. Invalid Email Format
    • Ensure new owner email is valid format
    • Email must match a registered UALS teacher account

Problem: New Owner Doesn't See Pending Transfer

Symptoms: New owner logs in but no "Pending Ownership Transfers" section visible

Debugging Steps

Verify Email Match

// New owner checks their login email
const user = await UALS_Auth.getUser();
console.log('Logged in as:', user.email);

// Compare with ownershipTransferPending
const classData = await fetch('/api/school/classes', {
  credentials: 'include'
}).then(r => r.json());

classData.classes.forEach(cls => {
  if (cls.ownershipTransferPending) {
    const pending = JSON.parse(cls.ownershipTransferPending);
    console.log('Transfer pending for:', pending.newOwnerEmail);
  }
});

Solution: If emails don't match, current owner must cancel and re-initiate with correct email.

Hard Refresh Dashboard

Press Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac) to clear cache and reload.

Check Transfer Was Actually Initiated

Current owner should verify in class details that transfer shows as pending.

Problem: Transfer Accepted But Still Can't Access

Symptoms: New owner accepted transfer but gets 403 errors on content editing

Solutions

  1. Refresh Dashboard

    Hard refresh (Ctrl+Shift+R) or reload the page to update permissions.

  2. Check teacherEmail Field
    // Verify class ownership updated
    const classData = await fetch('/api/school/classes', {
      credentials: 'include'
    }).then(r => r.json());
    
    const myClass = classData.classes.find(c => c.id === 'class-xxx');
    console.log('Owner is now:', myClass.teacherEmail);
    
    // Should match your email after acceptance
  3. Clear Browser Cache

    Settings → Privacy → Clear browsing data → Cached images and files

Enrollment Code Issues

Problem: Students Can't Enroll with Code

Symptoms: "Invalid enrollment code" error when students try to join

Common Causes

  1. Typo in Code
    • Enrollment codes are case-sensitive
    • Format: ENROLL-ABC12345 (8 characters after dash)
    • Solution: Copy-paste the code instead of typing
  2. Code Changed
    • Owner may have regenerated the enrollment code
    • Solution: Get updated code from teacher
  3. Class Archived
    • Archived classes don't accept new enrollments
    • Solution: Contact class owner to restore class

Problem: Need to Regenerate Enrollment Code

Scenario: Enrollment code was shared publicly by mistake.

Via UI

  1. Open class details modal
  2. Click "Edit" button
  3. Change enrollment code field to a new value
  4. Click "Save"
  5. Share new code with legitimate students only

Via API

await fetch(`/api/school/class/${classId}`, {
  method: 'PATCH',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    enrollmentCode: 'ENROLL-NEWCODE1'
  })
});

xAPI LRS Connection Issues

Problem: Classes Not Loading

Symptoms: Dashboard shows "Loading..." indefinitely, API returns 500 errors

Debugging Steps

Check Server Logs

Look for xAPI connection errors in server console:

// Common error messages:
"Failed to connect to LRS"
"LRS authentication failed"
"xAPI statement retrieval timeout"

Verify LRS Configuration

Check environment variables are set correctly:

LRS_ENDPOINT=https://your-lrs.com/xapi
LRS_USERNAME=your_username
LRS_PASSWORD=your_password

Test LRS Connection

// Run health check
npm run health:quick

// Or test xAPI specifically
npm run test:xapi

Check LRS Statement Retrieval

Verify statements are being retrieved correctly:

// In xapiClient.js, check query filters
const result = await this.getStatements({
  agent: { mbox: `mailto:${userEmail}`, objectType: 'Agent' },
  verb: 'http://adlnet.gov/expapi/verbs/initialized',
  activity: 'https://uals.app/class/',
  related_activities: true
});

// Ensure not using getAllStatements() (deprecated)

Problem: Duplicate Classes Appearing

Symptoms: Same class appears multiple times in dashboard

Root Cause

Multiple xAPI statements exist for the same class ID due to updates. The getAllClasses() function should deduplicate by keeping only the most recent statement per class ID.

Solution

// In schoolData.js, ensure deduplication logic:
const classMap = new Map();
statements.forEach(stmt => {
  const classId = stmt.context.extensions['http://uals.app/class/id'];
  const existing = classMap.get(classId);

  // Keep newest statement
  if (!existing || new Date(stmt.timestamp) > new Date(existing.timestamp)) {
    classMap.set(classId, stmt);
  }
});

const uniqueClasses = Array.from(classMap.values());

Browser-Specific Issues

Safari: Cookie Restrictions

Problem: Safari blocks third-party cookies by default, breaking Auth v2.0

Solution

  1. Safari → Preferences → Privacy
  2. Uncheck "Prevent cross-site tracking"
  3. OR add exception for uals.app domain
  4. Refresh UALS dashboard

Chrome: Incognito Mode

Problem: Incognito mode blocks cookies, causing auth failures

Solution

  1. Chrome → Settings → Privacy and security → Cookies
  2. Select "Allow all cookies" or add exception for uals.app
  3. Alternatively, use normal mode instead of incognito

Firefox: Enhanced Tracking Protection

Problem: Strict tracking protection blocks necessary cookies

Solution

  1. Click shield icon in address bar
  2. Toggle "Enhanced Tracking Protection" to OFF for uals.app
  3. Refresh page

Developer Debugging Tools

Browser Console Utilities

Debugging Commands (F12 Console)
// Check authentication status
const user = await UALS_Auth.getUser();
console.log('User:', user);

// Fetch all classes
const classes = await fetch('/api/school/classes', {
  credentials: 'include'
}).then(r => r.json());
console.log('Classes:', classes);

// Check specific class ownership
const myClass = classes.classes.find(c => c.id === 'class-xxx');
console.log('Owner:', myClass.teacherEmail);
console.log('Creator:', myClass.creatorEmail);
console.log('Experts:', myClass.contentExperts);

// Verify you can edit
const canEdit = myClass.teacherEmail === user.email ||
                myClass.contentExperts.includes(user.email);
console.log('Can edit:', canEdit);

// Check pending transfers
classes.classes.forEach(c => {
  if (c.ownershipTransferPending) {
    console.log('Pending transfer:', c.id, JSON.parse(c.ownershipTransferPending));
  }
});

Server-Side Debugging

Add Debug Logging
// In canEditClassContent() function:
async function canEditClassContent(userEmail, classId) {
  console.log(`🔍 Permission check: user=${userEmail}, class=${classId}`);

  const classData = await getClassData(classId);
  console.log(`📋 Class data:`, {
    owner: classData.teacherEmail,
    creator: classData.creatorEmail,
    experts: classData.contentExperts
  });

  const isOwner = classData.teacherEmail === userEmail;
  const isExpert = classData.contentExperts?.includes(userEmail);

  console.log(`✅ isOwner=${isOwner}, isExpert=${isExpert}`);

  return isOwner || isExpert;
}

Health Check Commands

Terminal Commands
# Full health check (LLM, LRS, GCS)
npm run health

# Quick health check
npm run health:quick

# Test specific components
npm run test:xapi
npm run test:gcs
npm run test:openai

Getting Help

💡 Before Requesting Support

Gather the following information to expedite troubleshooting:

  1. Your user email and role
  2. Class ID experiencing the issue
  3. Exact error message or unexpected behavior
  4. Browser and version (e.g., Chrome 120.0)
  5. Steps to reproduce the issue
  6. Screenshots of error messages
  7. Browser console logs (F12 → Console tab)
  8. Network requests (F12 → Network tab, filter by "school" or "class")

Support Channels

📧

Email Support

Contact UALS technical support with detailed issue description and debugging information gathered above.

📖

Documentation

Review the complete documentation including API reference, user roles guide, and this troubleshooting page.

View Docs
🐛

Bug Reports

File bug reports with reproducible steps, expected vs actual behavior, and system information.

💬

Community Forum

Search for similar issues or ask questions in the UALS educator community forum (if available).