Quick start
Astrolabe is one package, astrolabe, with everything on by default: every built-in component, the default look plus
playful and notebook, English and Polish, and TipTap as the editor. Each way to use it is one snippet; every
default is one option away.
Install
Section titled “Install”npm install astrolabeThe script-tag bundles need no install: they are the files of dist/cdn/ in the package, on any CDN.
HTML with answers
Section titled “HTML with answers”One script tag, astrolabe.js: the page compiles the exercise and grades it in the browser, so the answers are in
the page source. This is examples/static-html:
<script type="module" src="https://cdn.jsdelivr.net/npm/astrolabe@0.1/dist/cdn/astrolabe.js"></script><style>astl-player:not(:defined) { visibility: hidden; }</style>
<astl-player locale="en" persist> <astl-doc lang="en"> <astl-page id="p1" label="Animals"> <p>An elephant is <astl-gap id="g1" answer="grey"><astl-alt>gray</astl-alt></astl-gap>, and a trout is a <astl-gap id="g2" answer="fish"></astl-gap>.</p> <astl-choice id="q1" multiple scoring="per-option"> <astl-prompt>Which animals are mammals?</astl-prompt> <astl-option id="a" correct>Otter</astl-option> <astl-option id="b">Trout</astl-option> <astl-option id="c" correct>Mole</astl-option> </astl-choice> </astl-page> <astl-page id="p2" label="Numbers"> <p>Half of one is <astl-gap id="g3" type="number" answer="0.5"></astl-gap>.</p> <astl-choice id="q2"> <astl-prompt>How many legs does a spider have?</astl-prompt> <astl-option id="a">Six</astl-option> <astl-option id="b" correct>Eight</astl-option> <astl-option id="c">Ten</astl-option> </astl-choice> </astl-page> </astl-doc></astl-player>locale is the language of the buttons and messages, lang the language of the exercise (it decides how answers are
compared), and persist keeps progress in the browser. Serve the folder over http: (browsers load module scripts
and WebCrypto only from real origins, not file:); pnpm dev:static serves this example. Answer and press "Check":
An elephant is
Half of one is
pnpm astl validate examples/static-html/index.html --locale en prints every issue of the source in the terminal.
More: HTML with answers.
HTML + backend
Section titled “HTML + backend”Your server compiles the source into a public view and a private answer key, serves the view and grades checks and
submits. astrolabe/server has the engine with every built-in and the grading endpoint:
import { createEngine, createGradingHandler, createMemoryAttemptStore, PRACTICE_POLICY } from 'astrolabe/server';
// At least 16 bytes and stable: it decides every view. Only compile needs it; there is no default.const engine = createEngine({ secret: process.env.ASTL_SECRET });const result = await engine.compile(source, { salt: 'exercise-42' });if (!result.ok) throw new Error(result.issues.map((i) => engine.describe(i, 'en')).join('\n'));// result.view: public HTML, result.viewHash: its SHA-256, result.key: private, result.metadata: public facts
const attempts = createMemoryAttemptStore(); // your database in production (AttemptStore)const grading = createGradingHandler({ engine, attempts });const attempt = await attempts.create({ key: result.key, policy: PRACTICE_POLICY });// In your router, after checking that the signed-in student owns the attempt:// POST /api/attempts/:id/:action -> grading(request, { attemptId: id })The page loads astrolabe-player.js, which contains no engine and no answers; integrity is the viewHash, so the
player refuses a view that changed on the way:
<script type="module" src="https://cdn.jsdelivr.net/npm/astrolabe@0.1/dist/cdn/astrolabe-player.js"></script><astl-player locale="en" src="/v/3b1f....html" integrity="3b1f..." endpoint="/api/attempts/123"></astl-player>With a bundler, call definePlayer() from astrolabe/player instead of the script tag. pnpm dev:server starts
examples/node-server (set ASTL_SECRET first: export ASTL_SECRET="$(openssl rand -base64 32)"). More:
HTML + backend, with Node, Hono and Cloudflare Workers snippets.
Authoring
Section titled “Authoring”<astl-editor> is the exercise editor: TipTap with an insert menu, an inspector for answers and settings, a live
issues list and a preview that grades. value is the source, astl-editor-change says it changed, and components
picks the types authors can insert:
<astl-editor locale="en" components="astl-gap astl-choice"></astl-editor><script type="module"> import { defineEditor } from 'astrolabe/editor';
defineEditor(); const editor = document.querySelector('astl-editor'); editor.value = '<astl-doc lang="en"><p>An elephant is <astl-gap id="g1" answer="grey"></astl-gap>.</p></astl-doc>'; editor.addEventListener('astl-editor-change', () => save(editor.value)); // save: your own code</script>A bundler resolves astrolabe/editor; without one, load astrolabe-editor.js from the CDN instead, which defines the
editor on load and exports defineEditor and the rest. TipTap is a chunk of its own, loaded only when the editor
creates its own surface: with your editor in slot="surface" and its adapter, it is never loaded. The kit never
publishes: validate the source (editor.authoring.validate(editor.value)) and store it. More:
Editor and the TipTap example.
Change a default
Section titled “Change a default”Every option replaces its default and is never merged with it. The built-in lists are exported from the entries
(builtInDefinitions, builtInElements, builtInEditors, builtInStyleTemplates, builtInManifests): extend one
with [...builtInElements, mine], restrict it with .filter(...). English and Polish messages, the built-in slot
kinds and DEFAULT_PROFILE are always on; catalogs, slotKinds and profile add to them.
| Option (where) | Default, and how to change it |
|---|---|
components (createEngine, defineEditor) |
builtInDefinitions. Extend or filter it. |
elements (definePlayer, defineEditor) |
builtInElements (lazy); in defineEditor, those of its components. Extend or filter them. |
editors (defineEditor) |
The builtInEditors of its components. Extend or filter them. |
styleTemplates (all three) |
builtInStyleTemplates: playful, notebook. Extend or filter them; [] keeps only the default look. |
catalogs (all three) |
None: English and Polish are always on. [myWording] adds yours, and its keys win. |
slotKinds, profile, urlPolicy (createEngine, defineEditor; urlPolicy in definePlayer too) |
None, DEFAULT_PROFILE, isSafeUrl. Slot kinds add, profile fields override, urlPolicy replaces: give every part the same one (URLs). |
secret (createEngine) |
None: compile throws. Pass { secret: process.env.ASTL_SECRET }. |
compileSource (definePlayer) |
None: views only. createSourceCompiler() from astrolabe/engine compiles sources in the page. |
locale, theme (<astl-player>, <astl-editor>) |
Own lang, else the browser, else en; auto. Set the attribute. |
style-template (<astl-player>) |
None: the default look. playful, notebook or your own. |
components, adapter (<astl-editor>) |
Every registered type; TipTap. A tag list; any adapter with a slot="surface" child. |
createEngine is in astrolabe/engine and astrolabe/server, definePlayer in astrolabe/player, defineEditor
in astrolabe/editor. The Looks guide shows the three looks and how to write your own.
Next steps
Section titled “Next steps”- Concepts: source, view, key, slots, policies, pages and the two languages.
- Content format: every element and attribute you can write.
- Playground: edit a source and see its issues, view, key and the player side by side.