Under the hood

You don't need this page to author a course. It's here so you know what's possible when a project pushes past the defaults, and what your assistant is working with when it says yes. Every topic below is specified in full in the technical guide your assistant reads.

Course configuration

Each course has one configuration file, course.config.js, covering title and identity, branding (logo, primary colour, font), navigation mode (free or sequential), the completion rule, what counts as a pass, the passing score, the export standard, and accessibility strictness. Everything has a sensible default; a bare title is a valid config. The config is data only: anything that has to be a function, like custom access rules or xAPI credential lookups, goes in a companion course.runtime.js. Your assistant edits it from sentences like "make navigation sequential and raise the pass mark to 80".

The hooks API

Seven hooks are the stable contract between custom components and the runtime: questions, quiz control, navigation, progress, completion, saved state, and course identity. The built-in components are built on these same hooks, so anything a built-in does, a custom component can do too. The point of the design: a custom widget that registers through the question hook is scored, persisted, and reported to the LMS identically to a built-in question type. Custom work is never tracking-blind.

The saved-state hook is worth calling out: a custom interaction can persist a learner's in-progress work, and it survives a reload or a return the next day on every export target, browser storage on the web, the LMS's own saved state under SCORM, and the record store's under cmi5 and xAPI. A half-finished interaction resumes exactly where the learner left off, with no work on your part beyond storing the value.

Custom layouts and quiz shells

Two drop-in files change a course's chrome without forking anything: layout.svelte replaces the default sidebar-and-topbar shell, and quiz.svelte replaces the quiz presentation while the underlying scoring machinery stays put. A custom layout can also wrap the default one instead of starting from scratch.

Custom access rules

Beyond free and sequential navigation, a course can define its own access logic: prerequisites, unlocking a module only after a specific quiz score, time-based gating. It's a small function in course.runtime.js with full access to progress and course structure.

xAPI destinations

Components send custom xAPI statements through the useXAPI() hook. They can flow to the LMS's own record store (for cmi5/xAPI launches), to an external LRS, or fan out to several destinations at once, each with its own id, queueing, and retry. Statements carry learner identity automatically, on SCORM exports too, where the runtime derives the actor from the LMS session. One hard rule: course.config.js and course.runtime.js both ship inside the package where anyone can read them, so LRS credentials must come from a token endpoint, fetched by a function in course.runtime.js, never be pasted into either file.

Assets and styling

Media lives in an assets/ folder per course and is referenced through the $assets/ alias, which is checked at build time: a missing file is reported instead of silently shipping a broken image. Styling layers from coarse to fine: branding config for colour and font, a set of --tessera-* CSS custom properties for theming, and a styles/ folder that overrides anything.

Build customisation

The build itself is owned by Tessera (there's no bundler config to maintain), with two escape hatches: tessera.config.js merges partial build settings on top, and web exports emit a baseline Content-Security-Policy that can be extended when a course legitimately loads external fonts or media.

The guardrails

A few constraints keep courses portable and updatable, and your assistant observes them: page content is static (no runtime data fetching inside pages), only the public API is used (never internal paths that change between versions), and framework behaviour is extended through the extension points above, never by patching installed files.

The full reference

The canonical specification of all of this, hooks signatures, config fields, xAPI behaviour per standard, recipes, ships inside every workspace and updates with the framework: AGENTS.md. It's written for your assistant, but it's readable, and if you want to know exactly how something works, it's the source of truth.

Copied