Developer Reference Guide
Last Updated: 2026-10-17 | 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.
Central theme authority
The admin application theme controls learner presentation, including authored and LLM-generated instructional markup. Content supplies semantic headings, emphasis, lists, tables, figures, and choices underneath that theme. SPARC owns instruction and rendering semantics; it does not own an independent visual design.
Shared application rules live in mofacts/public/styles/classic.css; instructional presentation lives in the centrally loaded mofacts/public/styles/instructional-content.css. Reuse existing application properties for spacing, colors, typography, borders, rounding, shadows, focus, feedback, and motion. Semantic tables share one application rule whether or not they have Bootstrap's .table class. Figure rounding and table cell padding must track the theme. Do not restore local learner-renderer styles or add a parallel SPARC token system.
See the repository's docs/theme-presentation.md for the exact ownership and focused checks. Review Default and Dark Industrial at desktop and narrow widths, then restore the original personal theme. Presentation review must preserve instructional behavior, content, and durable learner records.
The instructional content library also uses the learner's SparcNode renderer and shared SparcFigure presentation. Its thumbnails clip the complete rendering; they do not truncate Markdown or apply a separate font/table style. Library wrappers retain inspection, revision, reuse, lazy loading, and full-size image access. Inspection choices are read-only and cannot write learner history; answer metadata remains outside the learner preview.
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
Experiment sign-in uses the template's existing isLoggingIn state to own form visibility during launch. The theme autorun and its queued paint callback must not reveal the form while that state is true or after the computation has stopped. Failed launches restore the form through restoreVisibleSignInScreen. Run node --test scripts/signInVisibility.test.cjs from mofacts/ to check the authentication/paint timing cases without starting a Meteor test server.
Content ownership transfer uses tdfOwnershipTransfer.ts and the existing lesson-family resolver, with tdfOwnershipTransferMongo.ts owning one snapshot/majority transaction. Selecting a root or condition moves the complete family only when the caller owns every member, the recipient exists, condition filenames match their canonical IDs, and no other root shares those conditions. Incomplete or previously split families fail before ownership changes. IDs, experiment targets, allocation/progress, media references, access grants, and histories remain intact; TDF revisions increment. Deleted content is not restored. Run node --test scripts/tdfOwnershipTransfer.test.cjs from mofacts/ for focused regressions.
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.
Progressive course launch rechecks the current course, owner/admin status or live section enrollment, course dates and assignment release before returning member lesson content. Cached course snapshots provide browsing data and cannot authorize former enrollment. Removing a section removes its enrollment references and invalidates existing assigned/public snapshots that reference the course.
Course snapshot version 5 includes a server-owned expiresAt deadline for the next relevant course start/end, assignment release or overdue-label change. Hidden future courses contribute their start boundary. Each response adds a fresh serverNow timestamp without persisting it; the browser uses the server-relative interval and a monotonic clock rather than its wall clock. The cache rebuilds on expiration without requiring new practice history, and the open Courses page schedules one deadline refresh plus a focus/visibility expiration check. Old derived caches rebuild lazily; learner data is not migrated. Teacher discovery uses current Roles assignments rather than historical fields on user documents.
Required experiment-state initialization and transition saves must acknowledge a persisted state identity before publishing local state. Rejections enter the existing terminal runtime error state, disable input, and stop audio; the current inline error/reload workflow owns recovery. A saved resume state and intentionally transient progressive practice retain their existing paths. Failure diagnostics omit raw state, event and learner payloads.
Mapped stimulus retrieval validates the saved permutation against the loaded cluster count and rejects missing mappings, invalid shuffled indexes and missing mapped clusters at runtimeStimuli.getStimCluster. Engine selection and card construction retain the concrete failure through the existing runtime error path. An authored empty cluster remains valid; a missing cluster is never replaced with an empty one.
The app's runtimeStimuli.ts now reuses one interpreted deck snapshot and one permutation validation across engine construction. Reactive source/mapping changes evict them immediately; clearRuntimeStimulusCache releases both at launch preparation and full cleanup. Each lookup clones only the selected cluster, preserving mutation isolation. The shared interpretation and shuffle/swap algorithms remain in learning-components/content/tdf/; assessment final ordering uses that same permutation owner.
Pedagogical dialogue policy lives in learning-components/units/sparcsession/sparcDialogueOpenRouterAdapter.ts, behind explicit transport, embedding, checkpoint/persistence, media and diagnostic dependencies. Its app factory retains Meteor and browser cache ownership. Blocks placement, scoring, question gates and replenishment live in learning-components/runtime/blocksGameEngine.ts; app machine/session state and Svelte presentation remain in mofacts/. These ownership changes preserve the current dialogue policies and Blocks practice alternative.
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.
Dashboard entry normally applies only newly committed history to lessons with new activity. Each accepted history row receives a per-user historySequence atomically with the server-only HistoryCommitState counter; duplicates receive no new position. The dashboard reads the range after lastHistorySequence and commits totals plus the processed position in one snapshot transaction. It retains unchanged lesson statistics and preserves the existing distinct-item/day and SPARC counting rules. The cached historyTdfTargets records which root each source lesson contributed to; a changed grouping encountered with new activity triggers an explicit repair. Full reconstruction is reserved for initialization, old/incomplete caches and repair, including reset, archival and restore. Existing histories require no backfill. See the public repository's docs/architecture.md for ownership, indexes and verification.
- 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
Owned-lesson exports issue five-minute single-use URLs through downloadDataByTeacher and downloadDataById; /data-download/owned-history revalidates the exact owned scope before source reads and again before headers. Projected history queries group up to 500 lesson IDs with 500-row cursor batches. boundedExternalSort.ts preserves the existing student/time ordering with bounded memory and private temporary files; normal completion, cancellation and failure remove the run. Canonical startup removes abandoned marked runs only after verifying the owning process is gone. Treat scratch files as sensitive learner data. The personal-history endpoint retains its separate authenticated-self scope.
filteredUsers retains bounded page membership and publishes live role assignments only for those page users, projected to the fields consumed by the role UI. The obsolete userHistory publication and getOutcomesForAdaptiveLearning method are removed. Use the incremental dashboard cache and getAdaptiveOutcomeRows, respectively. listSparcMoveLibrary remains available pending confirmation of external consumers; the current management UI uses the unified instructional listing.
Course startup does not convert historical assignments. Assignments without a supported lesson or progressive type are retired and must be recreated through the current assignment editor; current-format assignments remain usable. Creation and editing validate the current contract and invalidate course snapshots. Removing the conversion does not delete lesson packages, learner histories, memberships, or model state. Retained migration checkpoints and plan fields are inert and are not resumed during startup. See the public assignment creation and retirement contract.
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.
Fade-out completion belongs to an invoked actor in transition.fadingOut. The app injects a DOM-bound wait that observes the outgoing element's opacity animation and verifies its hidden final state. The machine's onDone guards perform the existing prepared-card handoff or clearing; onError handles genuine animation/element failures. State exit aborts the wait, so a late completion cannot advance another card. A wall-clock deadline must not classify browser rendering throttling as a lesson failure. Configured zero-duration fades complete after the DOM update confirms the outgoing card is hidden. The CSS theme, history finalization order, and next-card selection contracts are unchanged. Native transition events remain diagnostic and cannot separately commit a trial.
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
Related Pages
- Local Install
- Deployment Guide
- Settings-json Reference
- Learning Algorithms Reference
- Troubleshooting
Content Upload metadata cache
contentUploadMetadata supplies authenticated, projected metadata through Meteor Change Streams.
The server owns access checks, indexed dependency observation and batched asset-count queries;
the browser owns table summaries, ordering and pagination. client/lib/browserSnapshotStore.ts
is shared with the Practice menu, whose versioned snapshot keys and refresh semantics are preserved.
Content Upload keeps its own per-user snapshot, displays it immediately on navigation/reload, and
reconciles complete membership before persisting initial live data. Subsequent changes update
only affected summaries. The application logout hook clears upload snapshots even when the page
was not opened in that session. Page destruction stops observers; reconnect reconciles membership.
Only compact metadata is retained: no API-key values, full lessons, stimulus bodies, media bytes
or learner records. Cached display never authorizes an action. Storage/sync failures are reported
inline with retry. There is no polling or secondary data-loading path. Details/media remain lazy.
See the repository's mofacts/docs/content-manager-query-audit.md for the superseded read-path
inventory and the current cache contract. Isolated checks run with
node --test scripts/contentUploadCache.test.cjs; live reactivity requires the supported Meteor
runtime. This change does not modify content schemas or require a data migration.