Skip to content

Feat/export scorm12#370

Open
jaumemir wants to merge 12 commits intoTHU-MAIC:mainfrom
jaumemir:feat/export-scorm12
Open

Feat/export scorm12#370
jaumemir wants to merge 12 commits intoTHU-MAIC:mainfrom
jaumemir:feat/export-scorm12

Conversation

@jaumemir
Copy link
Copy Markdown

@jaumemir jaumemir commented Apr 4, 2026

Summary

Adds a complete SCORM 1.2 export pipeline to OpenMAIC. Instructors can export any course as a standards-compliant .zip package that can be uploaded to any SCORM 1.2 LMS (Moodle, SCORM Cloud, etc.). The package is a single-SCO design: all scenes live in one index.html with internal JS navigation, which avoids the iframe navigation failures that multi-SCO approaches suffer in most LMSes.

Related Issues

No related issues.

Changes

  • New export pipelinelib/export/scorm/ with dedicated modules: course-builder.ts, slide-sco.ts, quiz-sco.ts, interactive-sco.ts, asset-collector.ts, manifest.ts, scorm-bridge.ts, use-export-scorm.ts
  • Single-SCO architecture — all scenes as <section> elements in one index.html; internal JS navigation replaces inter-SCO redirects
  • Slide rendering — absolute-positioned canvas with transform: scale() fit; SVG shapes use preserveAspectRatio="none" and inline <linearGradient>/<radialGradient> defs to match editor appearance
  • TTS audio export — speech audio fetched from IndexedDB (db.audioFiles) when no server URL is available; included in assets/audio/ inside the ZIP
  • Quiz scenes — single and multiple choice questions with immediate visual feedback (correct / wrong / missed); submit requires all questions answered; failed attempts show a retry button with infinite retries
  • Interactive scenes — embedded in a sandboxed <iframe srcdoc>
  • Left sidebar — fixed 240 px navigation panel listing all scenes with visited state (green checkmark) and a progress bar; locked scenes are dimmed until unlocked
  • Sequential navigation — slide/interactive scenes unlock the next scene on visit; quiz scenes only unlock the next scene after passing (≥ 80 %); sidebar and Next button enforce this
  • SCORM progress reportingcmi.core.lesson_location + cmi.suspend_data saved on every navigation and quiz submit; on re-entry the course resumes at the last scene with visited state and quiz scores restored; completed/passed status is never downgraded to incomplete
  • CSS isolation — all class names use the om- prefix to prevent collisions with LMS stylesheets (Bootstrap, Moodle, etc.)
  • notes.txt — plain-text file included in the ZIP with each scene's title and its TTS narration text, mirroring the notes panel in the OpenMAIC editor
  • Export dialog — new ScormExportDialog component with an "include videos" toggle, accessible from the header

Type of Change

  • New feature (non-breaking change that adds functionality)

Verification

Steps to reproduce / test

  1. Open a course in OpenMAIC that contains at least one slide with TTS narration, one quiz (single/multiple choice), and one interactive scene.
  2. Click Export → SCORM 1.2 in the header and confirm the download.
  3. Unzip the package and verify the structure: imsmanifest.xml, index.html, scorm_bridge.js, notes.txt, and an assets/ folder with audio files.
  4. Open index.html locally in a browser: verify sidebar navigation, slide scaling, audio playback, quiz submission with visual feedback, and retry on failure.
  5. Upload the ZIP to a SCORM 1.2 LMS (e.g. SCORM Cloud or Moodle): verify that navigation, scoring, and completion status are recorded correctly.
  6. Exit the course mid-way and re-enter: verify the course resumes at the correct scene with visited state and quiz scores preserved.

What you personally verified

  • Slides render correctly at all window sizes (scale fit, no letterboxing of SVG shapes including gradient fills)
  • TTS audio files are included in the ZIP and play back in the correct order
  • Quiz submit is blocked until all questions are answered; orange warning highlights unanswered questions and clears in real time when an answer is selected
  • Failed quiz shows a "Torna-ho a intentar" retry button that fully resets the form; passing unlocks the Next button and the next scene in the sidebar
  • Two quizzes in the same course work independently (duplicate question IDs across quizzes no longer conflict — each block ID is prefixed with the scene index)
  • Resume/bookmark works against LMS: re-entering the course navigates to the last scene and restores visited state and quiz scores
  • notes.txt contains the correct narration text for each slide scene (not needed, but as a bonus)
  • Not verified: behaviour on Moodle or other LMSes beyond a custom developed LMS compatible with SCORM v1.2

Evidence

  • CI passes (pnpm check && pnpm lint && npx tsc --noEmit)
  • Manually tested locally
  • Screenshots / recordings attached (if UI changes)

Development

Development realized by Claude Code, under supervision of Author.

Miralles Solé and others added 12 commits April 4, 2026 19:18
Adds a new export option that generates a SCORM 1.2-compliant ZIP package
uploadable to any LMS (Moodle, Blackboard, SCORM Cloud, etc.).

What is exported:
- Slide scenes → HTML SCO with absolutely-positioned CSS canvas, responsive
  scaling via transform:scale(), and auto-playing TTS narration audio
- Quiz scenes (single/multiple choice only) → HTML SCO with SCORM scoring
  (cmi.core.score.*, cmi.interactions.*), visual feedback, and pass/fail tracking
- Interactive scenes with embedded HTML → HTML SCO wrapping content in an
  isolated <iframe srcdoc>

What is excluded:
- PBL scenes (require live LLM)
- Whiteboard animations and multi-agent chat
- Short-answer quiz questions (require AI grading)
- Interactive scenes without html content

Video handling: a pre-export dialog lets the user choose between including
video files in the package or replacing them with poster images (recommended
to stay within LMS upload limits).

New files:
- lib/export/scorm/ (scorm-bridge.ts, manifest.ts, asset-collector.ts,
  slide-sco.ts, quiz-sco.ts, interactive-sco.ts, use-export-scorm.ts, index.ts)
- components/scorm-export-dialog.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All scenes now rendered as <section> elements in a single index.html
  instead of separate scos/scene_XX.html files per scene (fixes broken
  navigation in LMS iframes that blocked window.parent.location.href)
- Slide scaling uses .slide-scaler wrapper sized to visual dimensions so
  flex centering works correctly (fixes deformed slide layout)
- TTS audio URLs are fetched as blobs and included in ZIP under assets/
- SCORM completion only fires on last scene (not on every scene load)
- Nav bar with Prev/Next + narration Play/Pause/Restart controls embedded
  in single page; Next button gated on quiz submission
- course-builder.ts assembles all section fragments into index.html
- manifest.ts rewritten for single-SCO (one item → index.html)
- data-title attributes added to all section types for nav bar display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shape rendering:
- Replace invalid CSS linear-gradient() in SVG fill attribute with proper
  SVG <defs><linearGradient>/<radialGradient> elements
- Shapes with gradient fills now render correctly (previously appeared as
  white/transparent boxes because browsers ignore CSS gradients in SVG fill)

TTS audio:
- TTS audio in normal flow is stored in IndexedDB only (audioUrl is not set)
- asset-collector now falls back to db.audioFiles.get(audioId) when audioUrl
  is absent, storing the blob in the ZIP with key idb:{audioId}
- slide-sco narration lookup updated to resolve both audioUrl and idb: keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rendering fix — CSS class name conflicts with LMS stylesheets:
- LMSes inject their own CSS (Bootstrap, Moodle themes) that overrides common
  names like .scene, .option-label, .submit-btn, .result-bar, .analysis
- Renamed ALL CSS classes to om- prefix: om-scene, om-slide, om-quiz,
  om-scaler, om-canvas, om-opt, om-submit, om-result, om-pass/fail, etc.
- Removed unnecessary .slide-stage wrapper div (was causing flex layout issues
  since position:absolute canvas made the wrapper collapse to zero height)
- om-scaler is now the direct flex child of om-slide, centering works correctly

Sidebar navigation:
- Fixed 240px left panel with course title, progress bar, and scene list
- Each scene shows a dot indicator: empty (not visited), green ✓ (visited),
  blue (current scene)
- Progress label "X / N completades" updates as user navigates
- Clicking any item in the sidebar navigates freely (no quiz gate)
- Scene area and nav bar are offset by sidebar width (left: 240px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SVG shapes use normalized 0-1 coordinates in the path. Without
preserveAspectRatio="none", the browser letterboxes the square (1x1)
viewBox to fit within the element bounds using xMidYMid meet — so a
1000x130 header shape only renders in a 130x130 area centered
horizontally, leaving the rest empty.

Adding preserveAspectRatio="none" stretches the viewBox to fill the
element exactly, equivalent to PPTist's scale(W, H) transform approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Saves lesson_location (scene index) and suspend_data (visited bitmask +
quiz scores) on every navigation and quiz submit. On re-entry, restores
visited state, quiz scores, and resumes at the last scene. Guards against
overwriting completed/passed status with incomplete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Some LMS implementations (e.g. MiniLMSCat) load saved state asynchronously
after LMSInitialize returns. Poll suspend_data/lesson_location up to 10 times
(50ms apart) before applying resume state, so the course resumes correctly
even when the LMS fetch takes a moment to complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sequential unlock: slide/interactive scenes unlock the next scene on
  visit; quiz scenes only unlock the next on passing (score >= 80).
  Locked scenes appear dimmed in the sidebar and cannot be clicked.
  unlockedUpTo persisted in suspend_data field 'u'.

- Quiz retry: failed quiz shows "Torna-ho a intentar" button that fully
  resets inputs, styles and analysis so the student can retry. Infinite
  retries allowed; passing any attempt unlocks the next scene.

- Submit validation: submit button requires at least one answer per
  question; unanswered blocks are highlighted in orange until resolved.

- Init timing: reduce retry attempts from 10x50ms to 5x100ms now that
  MiniLMSCat bridge loads state synchronously before the iframe starts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n real time

- Add null guard (if !block return) in submit validation and retry reset
  to prevent silent TypeError if getElementById finds no element.
- Clear om-qblock--warn class immediately when user selects an answer,
  giving real-time feedback instead of requiring a second Submit click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…deList

- Add if(!block) guard in scoring forEach to prevent silent TypeError
  if any question block is unexpectedly missing from the DOM.
- Replace NodeList.forEach() with Array.from().forEach() throughout
  to avoid compatibility issues in some browser/LMS environments.
- Guard SCORM.logInteraction with typeof check before calling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… conflicts

Multiple quizzes in the same course can have questions with identical IDs
(e.g. q1, q2, q3). All quiz sections are in the same DOM, so getElementById
was returning the first quiz's hidden blocks instead of the current quiz's.
This caused submit validation to always fail silently on the second quiz.

Fix: prefix all qblock_* and analysis_* element IDs with the sceneIndex
(e.g. qblock_12_q1) to guarantee uniqueness across the entire page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generates a notes.txt file at the ZIP root with all scene titles and
their associated TTS speech text (SpeechAction.text), mirroring the
notes panel visible in OpenMAIC's editor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant