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, the passing score, the export standard, and
accessibility strictness. Everything has a sensible default; a bare title is a valid
config. Your assistant edits it from sentences like "make navigation sequential and
raise the pass mark to 80".
The hooks API
Six hooks are the stable contract between custom components and the runtime: questions, quiz control, navigation, progress, completion, and saved state. 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 cmi5. 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.
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 with full access to progress and course structure.
xAPI destinations
Custom xAPI statements 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 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: the course configuration ships inside the package where anyone can read it, so LRS credentials must come from a token endpoint, never be pasted into config.
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
fails the build instead of 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.