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.