Developer Reference Guide
Last Updated: 2026-10-09 | For: Developers working in C:\dev\MoFaCTS
Audience: Developers only. Students, teachers, and researchers should use the role guides from Home unless they are also contributing code.
This is the canonical developer doc for the wiki. It replaces the previous map, architecture, data model, API-contract, API-index, and card/state-machine reference pages.
Start Here
- Use Local Install for the default local setup.
- Read the code in
mofacts/before changing runtime behavior. - Use this page as the stable reference for architecture, data model, runtime boundaries, and high-risk areas.
Source of Truth
- The wiki is canonical for stable contributor guidance.
- The code is canonical for exact runtime behavior.
- Public release-facing docs live in the repository root
docs/folder; longer operational/user guidance lives in this wiki.
Codebase Map
MoFaCTS owns its Meteor shell and persistence under mofacts/. Executable pedagogical behavior lives under root learning-components/ and receives explicit runtime dependencies from the application.
MoFaCTS/
mofacts/ Meteor app shell, UI, server, common collections, custom Meteor packages
learning-components/ unit engines, adaptive model code, content interpretation, runtime contracts
examples/ typed contributor examples
deploy/ canonical Docker Compose, hotfix, and deployment workflow
app/ target app-shell architecture map during migration
packages/ target public API packages during migration
Runtime Architecture
Lesson launch preparation
Learner delivery uses /content/:tdfId, mounted through contentRoute.ts, with instruction entry through /instructions/:tdfId. lessonRoute.ts and activeLessonRoute.ts carry the root lesson, course-assignment launch descriptor, and practice mode across navigation and reloads. A course-only lesson requires its course descriptor; an arbitrary direct URL cannot create enrollment or assignment access. contentSurfaceInit.ts coordinates bootstrap; contentLaunchCoordinator.ts tracks content resolution, progress restoration, engine initialization, first-trial preparation, first render, and activation. Input opens only after activation and runtime readiness. Launch identity retains learner, root TDF, active TDF, unit, and attempt separately. Missing identity or an invalid phase transition fails clearly.
mofacts/client/lib/lessonLaunchInitializer.ts owns shared launch-ready content and progress preparation for the dashboard and route launcher. It applies initializeLessonLaunchEntry before returning, so first-unit instruction identity exists even when a cold route defers navigation. Keep audio warmup, learner overrides and loading feedback in their owning callers; do not duplicate instruction Session initialization there.
Cold instruction URLs with persisted progress enter the existing content-resume lifecycle, preserving the active lesson/course descriptor. That lifecycle restores the saved unit; history writers must not invent a unit number or weaken canonical history validation. Completed lessons retain their existing completion restriction.
Practice dashboard totals can be unknown (null). The shared practiceMetrics.ts formatter displays the practiced count alone in that case; only a supplied numeric total produces a denominator. Do not coerce an unknown total to zero or fetch full stimulus sets merely for this display.
Browser UI (Blaze app shell and management tools; Svelte learner runtime)
-> Meteor DDP / HTTP
Meteor app server
-> MongoDB
-> Dynamic assets / file storage
Key patterns:
- Meteor methods own database access, authorization, secrets, and external calls that cannot safely run in the browser. Learner computation belongs on the client through learning components.
npm run lint also checks mofacts/security-surface-contract.json. When methods, publications, HTTP handlers, exports or management routes change, review authorization and update that contract in the same change. Its access labels record intended policy; they do not enforce authorization or prove complete runtime coverage. Do not automatically classify newly discovered surfaces.
Learner analytics follows this boundary: server/methods/learnerAnalyticsMethods.ts supplies authorized, field-limited history pages with a bounded snapshot cursor. client/views/home/learningAnalytics/learnerAnalyticsCalculation.ts computes aggregates and model progress in the browser. The server does not maintain a learner-model computation cache. Both paths are under mofacts/.
- Publications handle reactive reads.
- Shared collections are defined in
mofacts/common/Collections.ts. meteorIntegration.tsmounts the Svelte learner runtime from the Blaze route.ContentSurface.sveltecomposes flashcard, video, SPARC, and dedicated AutoTutor surfaces; it does not own their pedagogical algorithms.
History-First Resume Architecture
Unit engines consume the canonical nested clusters from the active TDF's rawStimuliFile.setspec.clusters. Preserve authored cluster and stimulus identities. Do not reconstruct cluster structure by decoding flattened KC numbers or use a displayed filename as the runtime identity. Launch preparation must make the canonical stimulus source available before model initialization.
The current experiment runtime uses a history-first persistence model:
Historiesis the canonical committed interaction log for learning, assessment, hidden-item actions, and video checkpoint question progress.- Learning resume reconstructs model state from history, then recomputes the next best card at resume time instead of restoring a persisted per-card cache.
- Assessment resume derives question position from completed history count plus the persisted schedule artifact.
- Video resume derives checkpoint position from completed history count plus checkpoint metadata in the TDF.
GlobalExperimentStatesis now sparse control-plane state only: unit bootstrap, mapping/signature setup, schedule artifact storage, and other non-trial resume metadata.- Ordinary per-trial or per-question experiment-state writes are intentionally avoided.
SPARC continuation replays canonical document events for the authenticated learner, TDF, unit, and page, with course-assignment scope when applicable. sessionID identifies the attempt that wrote an event; it does not delimit durable document continuation. Authors supply sparcPages[].pageId; runtime derives pageKey and nested state addresses. Keep those IDs stable and use the app's sparcRuntimeState.ts hydration/replay boundary instead of treating model-practice history as document state.
Main Runtime Files
Use these first when you need to understand behavior:
mofacts/server/methods/mofacts/server/publications.tsmofacts/common/lib/openRouterClient.ts(shared provider request boundary; server code must not import browser implementation helpers)mofacts/common/Collections.tsmofacts/client/views/experiment/svelte/components/ContentSurface.sveltemofacts/client/views/experiment/svelte/services/sessionSurfaceMode.ts(surface selection and shared shell policy)mofacts/client/views/experiment/svelte/components/SparcSessionSurface.svelte(SPARC node actions, dialogue submission, and document progress UI)mofacts/client/views/experiment/svelte/services/sparcProductionRuleEvaluation.ts(app integration for production-rule evaluation)mofacts/client/views/experiment/svelte/machine/contentRuntimeMachine.ts(shared lifecycle)mofacts/client/views/experiment/unitEngine.ts(app dependency facade)learning-components/units/sparcsession/sparcAutoTutorRuleRuntime.ts(response events, registered evaluation/generation actions, and awaited production execution)learning-components/units/sparcsession/sparcProgressiveScaffoldingRules.ts(runtime-owned instructional productions)mofacts/client/views/experiment/svelte/services/sparcAutoTutorRuleCommit.ts(completed-turn persistence)learning-components/units/createUnitEngine.tslearning-components/units/UnitEngineRegistry.tslearning-components/trial-displays/sparc/sparcTrialDisplayEvaluation.ts(SPARC display response normalization, intent evaluation, and feedback matching)learning-components/content/response-assessment/responseAssessment.tsandcontent/response-normalization/learnerResponseNormalization.ts(pure answer comparison and normalization)mofacts/client/views/experiment/learnerResponseAssessment.ts(app policy and localized feedback projection;getDisplayAnswerTextselects the authored display answer)learning-components/content/tdf/runtimeStimulusInterpretation.ts(stimulus interpretation);mofacts/client/lib/runtimeStimuli.tssupplies app contextmofacts/client/lib/currentDeliverySettings.ts,studentPerformanceRuntime.ts, andthemeRuntime.ts(separate app runtime owners)learning-components/models/adaptive-logistic/selectionPolicy.tslearning-components/models/adaptive-logistic/probabilityCalculation.tsmofacts/client/views/experiment/svelte/services/cardRuntimeState.tsmofacts/client/views/experimentSetup/contentUpload.tsmofacts/client/views/experimentSetup/apkgWizard.ts
Data Model
Primary collection groups:
- learning and course data:
Tdfs,Items,Histories,Assignments,Courses,Sections,SectionUserMap - experiment/session state:
GlobalExperimentStates,UserTimesLog,UserMetrics,UserDashboardCache - content/assets/config:
Stims,itemSourceSentences,DynamicAssets,DynamicSettings,DynamicConfig - security/audit/utility:
PasswordResetTokens,AuditLog,ErrorReports,UserUploadQuota,ClozeEditHistory
Important relationships:
Course assignment authority comes from Assignments and SectionUserMap, not a copied list in learner loginParams. Dashboard caches are derived summaries. Rebuilds must use projected history fields, bounded cursor batches, supporting indexes, and progress reporting; listing pages must not hydrate full TDF/stimulus documents. History-write diagnostics may report timing, payload size, field presence, and event category, but must not log raw learner values.
- course -> section -> assignment
- TDF -> items -> histories
- user <-> section through
SectionUserMap - per-user runtime control state lives separately from lesson definition data, but committed trial/question behavior should be recoverable from
Histories
Notes:
- Meteor 3 server code should prefer async collection APIs.
- JS variable names and Mongo collection names do not always match.
DynamicSettingsstill uses the historical collection namedynaminc_settings.
API Surface
Local Meteor package overrides are intentional runtime dependencies. Review mofacts/packages/ddp-server/MOFACTS-OVERRIDE.md and mofacts/packages/rspack/MOFACTS-OVERRIDE.md before changing the pinned framework or removing an override. Their source provenance, narrow changes, and supported qualification requirements belong to those records; copying only application source does not reproduce the runtime.
For a clean dependency installation, run npm ci, then
node scripts/installRspackPackageDependencies.cjs from mofacts/, before compiling
Meteor plugins. CI and the Docker builder share this setup. The installer consumes
the committed local Rspack shrinkwrap, validates exact installed versions and
integrities, and preserves its source bytes. Meteor's normal installed-tree markers
allow compilation to reuse those pins. Captured-source checks and Docker checksum
checks remain blocking; dependency updates belong in reviewed source changes.
The acquisition boundary excludes only the regenerated Ostrio npm metadata and
WebGazer public assets (mofacts/packages/ostrio-files/.npm/ and
mofacts/public/webgazer/). Their source declarations, asset-preparation script,
licenses and dependency pins remain captured. CI verifies this with the actual
Docker acquisition target and still rejects altered tracked shrinkwraps.
The captured test workspace also includes the exact public workflow, guide and hotfix/Compose fixtures read by the Meteor harness. They are reviewed supplemental inputs with their own digest. CI runs that harness inside a captured workspace to check that its input closure is complete; private settings and local state remain excluded.
Rspack's _build, public/private build-assets and build-chunks, and .rsdoctor
output trees also stay outside source acquisition, including standard local-directory
suffix variants. Compiler source, configuration and locks remain included, and
the acquisition regression covers these generated output families.
Verified October 12, 2026: application commit 327c050b passed
GitHub CI, including
Linux source/capture contracts (75 capture checks), the Meteor harness, 795 server
tests and 1,104 browser tests with zero failures, final tested-source verification,
and the amd64 Docker build/smoke job. The matching
Security run also
passed. This verification did not deploy production or resolve the separate
dependency maintenance alerts.
For authorized local MoFaCTS browser diagnostics, use the MCP sidecar workflow in mofacts-mcp-sidecar/README.md. scripts/check-localhost-sidecar.ps1 inspects status without starting services; -Start or -Restart are separate service operations. A needed start provisions its dedicated local read-only MongoDB account and retains credentials in ignored local state. It must not receive production or app-user credentials.
Package upload clients share mofacts/client/lib/packageUploadClient.ts. fileUploadPolicy.ts distinguishes ZIP packages from lesson media and checks filenames, approved extension/MIME pairs, and required lesson/stimulus-set metadata. Upload intent and metadata do not replace server ownership checks. AI working drafts now belong to browser IndexedDB; the server owns authenticated provider calls and final package persistence.
Uploads use explicit DDP transport through the maintained local mofacts/packages/ostrio-files package. The authenticated invocation owns upload identity; continuation, finish and abort require the persisted pending owner, including after reconnect/restart. HTTP upload returns 410, missing owners require a fresh upload, and completed-file deletion stays in guarded asset methods. See upload ownership and transport and the package's maintenance record before upgrading it.
AI creation's shared orchestrator is mofacts/client/lib/aiContentPipeline.ts, with source extraction in aiContentWikipediaSource.ts, canonical image acquisition in aiContentWikimediaFiles.ts, prompts/contracts in their corresponding libraries, and local retention in aiContentWorkingStore.ts. Creator and Prompt Lab share this pipeline and review components. A source list remains authoritative when an item fails; missing prompts or images retain explicit reasons and block final save.
Management UI uses shared primitives under mofacts/client/lib/adminUi/ for asynchronous commands, loading/empty/error states, disclosures, inline confirmation, template lifetime, and route presentation. managementRoutePresentationPolicies.ts names authenticated routes, allowed roles, page titles, and shell modes. Reuse these owners when adding a management page; server authorization remains required independently of route visibility.
Use scopedAsyncCommandRegistry.ts to keep repeated row/card command results attached to their record; keep page loading in its separate loadable state. Inline status and confirmation belong beside the triggering control. The shared confirmation controller owns focus restoration and the trigger's ARIA relationship, and shared status templates announce feedback to assistive technology.
The raw method/publication inventory is large and not useful as a separate wiki page. The practical split is:
- auth/user management
- content upload and editing
- assignments and class management
- experiment/runtime state
- reporting and data export
- theme/help-page/admin utilities
High-value methods to know:
processPackageUploadanalyzeApkgFilegenerateTdfsFromApkgsaveTdfStimulisaveTdfContentcopyTdftdfUpdateConfirmeddownloadDataByTeacherdownloadDataByFiledownloadDataByIdgetTdfsAssignedToStudentgetAllCourseAssignmentsForInstructorinsertHistory
Server-internal reporting helper:
getHistoryByTDFIDis exported for server-only reporting code and should not be treated as a public Meteor method.
Important publication groups:
- content listing and editing:
allTdfsListing,dashboardTdfsListing,tdfForEdit,ownedTdfs - runtime/session:
userExperimentState,currentTdf - assets/settings/theme:
assets,settings,theme,themeLibrary
When documenting or changing a method, capture:
- auth/roles required
- input shape
- return shape
- side effects
- failure cases
Card Runtime and State Machine
Every registered engine implements learning-components/units/UnitEngine.ts: prepare and commit the next trial, advance after an answer, report completion, project the display question index, and clear prepared state. The app dispatches through this contract. Put unit-specific selection and progression in the owning engine; do not add another unit-type switch to the Meteor-facing service. Manifests declare required runtime capabilities, and registration fails when those capabilities are missing. The minimal example is examples/minimal-unit-type/MinimalFlashcardUnit.ts.
The experiment card flow is a high-risk area. Treat changes here as architectural work, not routine UI polish.
Runtime unit resolution recognizes learning, assessment, video, dedicated AutoTutor, SPARC, gaze calibration, and instruction-only units. Use the unit registry and lifecycle interfaces when extending delivery. SPARC document interaction and the standard response timer have separate ownership: production-rule documents suppress the standard timeout, while nodes alone do not. Worksheets have explicit work/review clocks. H5P authoring, upload, and delivery are unsupported in current source.
Core flow:
- the active unit engine selects and prepares the next trial
- content preloads and display state is set up
- the question starts fading in; trial timers are anchored here, before input may open
- surface-specific submission becomes a canonical response event for evaluation
- feedback/study/transition logic runs
- history commits and the unit lifecycle determine the next trial or unit transition
The machine distinguishes idle, presentation, awaiting input, review/study, feedback, transition, video, and error phases. Exact state names and nested transitions live in machine/constants.ts and the contentRuntimeMachine* files; inspect those definitions before sending an event or adding a guard.
Use these rules when editing trial behavior:
- input should only be accepted in input-accepting states
- speech recognition should not restart during feedback or transition phases
- fade-out/fade-in boundaries are part of runtime correctness, not just animation
- response timeout resets are explicit user-activity events, such as text input activity, voice start, and speech-recognition retry/error activity
- TTS may extend an expired timer until active playback completes, but it must not restart a full configured timer
- surface destruction must stop actors, subscriptions, timers, media, and event handlers; verify ownership before adding another cleanup guard
For timestamp provenance and export calculations, use Interpreting Trial Timing. Trace the UI event, state-machine timestamp, history field, export mapper, and TSV column separately. The exporter can reconstruct Time for older records that lack problemStartTime; client logging code alone does not establish the exported value. The current reveal-anchored response timer also differs from the older deployed cardMachine audio-gated timer documented there. Keep collection-build behavior distinct from the current source contract.
Card State Ownership
Card state is divided into domain services under the Svelte runtime's services/ directory, including timing, readiness, progression, audio, speech input, score, display, and SPARC state. Use the owning service rather than adding direct mutations in unrelated modules.
| Concern | Owning service |
|---|---|
| Active display, answer, and display indices | activeTrialDisplayRuntimeState.ts |
| Reveal/input readiness | trialReadinessState.ts |
| Trial and feedback timestamps, timeout handles | trialTimingState.ts |
| Question cursor | trialProgressionState.ts |
| Recording, transcription, and audio locks | audioRuntimeState.ts |
| Feedback values and lifecycle | feedbackRuntimeState.ts |
| Score display and scoring flags | scoreRuntimeState.ts |
| Hidden-item visibility | hiddenVisibilityRuntimeState.ts |
| Video state | videoRuntimeState.ts |
unitEngineRuntimeContext.ts adapts these services to explicit learning-component capabilities. Its Session read/write allowlists define the permitted boundary. Components should not reach into app internals or acquire a general state-store object.
State buckets you should think in:
- card UI state
- speech-recognition state
- trial-state-machine state
- timeout/timer state
- shared Session-backed lesson/runtime state
If you need a new state value, prefer:
- an existing store or facade
- a narrowly scoped new accessor
- direct raw Session usage only if there is a clear reason
Current Structural Risks
- server methods are split under
mofacts/server/methods/, but runtime-facing method changes are still high-risk - content launch, resume, history identity, and shared lifecycle transitions remain high-risk boundaries
- Blaze and Svelte coexist, which makes contribution targeting non-obvious
- root
learning-components/is executable TypeScript; unit engines and trial-display adapters register through explicit learning-component manifests - canonical history rows carry
historySchemaVersion: 1; standard trials use the shared history service, AutoTutor writes through its history runtime capability, and app-owned persistence validates the canonical envelope before storage. Historical H5P envelopes remain readable. - some older planning docs in the repo describe future intent, not current truth
Dependency and Change Policy
- use
npm ci, notnpm install, for reproducible installs - keep dependency updates focused
- prefer explicit image/version tags over floating
latest - run
npm run lintandnpm run typecheckbefore merging TypeScript-bearing app changes - root
learning-components/**/*.tsandexamples/**/*.tsare included in the repository checks - treat auth, upload, export, and runtime-trial changes as high-verification areas
Contribution Targeting
- default new UI work to the active Svelte direction only when the feature area already lives there
- trace the active route, mount, selected surface, unit engine, and service before choosing where to change behavior
- for content/import/export changes, update the user-facing wiki pages in the same change when behavior changes