Two ways through this page
Same tool, two readings of it. You can switch at any point from the top right.
Not sure? Take the designer's route. Nothing is hidden, only reordered.
Pilot build · Windows x64
F4xAPI Studio is a desktop course builder that hands you the reporting. You choose what counts as done, what an attempt is worth, and what the course should do about it while the learner is still inside it. Send someone back through the practice they need. Leave the reading they understood alone.
F4xAPI Studio is a desktop course builder for people who write code. Every block emits xAPI, and every statement passes through your override before it leaves the page. Sandboxed HTML blocks, Monaco exercises, a theme layer of custom properties and classes, and an open package format.
Authoring runs offline · Export needs a licence check · Windows 10 and 11, 64-bit
The problem this exists for
Twelve chapters. A simulation you are proud of. A final assessment with a pass mark someone agreed to in a meeting. It goes out, and six weeks later the LMS says 84% completed. That number is the whole report.
Completion says a learner reached the end. It says nothing about the route. Nobody can tell you that a third of them opened the glossary four times on chapter six, that the exercise everyone passes takes eleven attempts to pass, or that the elegant interaction on page two is being skipped by anyone who finds the Continue button first.
xAPI already carries that detail. Every interaction can become a statement: who did what, to which thing, with what result. The catch is that most authoring tools decide the statements for you, and their idea of "done" is usually "seen".
F4xAPI hands the statements back. You choose which interactions count, what an attempt is worth, and what a result means. You can read those signals while the learner is still in the course and act on them: reset a section someone clearly did not absorb, score a simulation on the decisions made rather than the outcome reached, send a learner back through the practice while leaving the reading they understood intact.
Instrument a course this way and it starts answering questions about itself. Where do people stall. What gets retried. Which explanation costs three attempts and which costs none. That is a design instrument as much as a course builder, and it is why this tool exists.
Completion tells you someone reached the end. It never tells you what the end cost them.
For learning designers
A loop, watched from the outside
The capstone of the built-in course is a 3D printer that runs and reports nothing. The learner has to wire up its reporting. Here is a run that goes wrong, and the rule that turns it into a lesson instead of a wall.
The commission. The machine works. Nothing it does reaches the course, so nothing the learner does is worth anything yet.
It sits in the properties panel beside the block, not buried in the content. Three failures, and the learner is returned to the exercises in the materials chapter. Only the exercises. The reading they already understood stays finished, because making someone re-read what they knew reads as punishment and they stop trusting the course.
Change 3 to 2 and the loop tightens. Change
'materials' and it points somewhere else. That is the whole
edit.
Autocomplete covers the whole scope, the built-in course walks you through this exact pattern, and a mistake here reports nothing rather than breaking the block.
Design decisions, not settings
A block can complete on sight, on any interaction, on every item being opened, on a score you set, or on a rule you write. The accordion that completes when one row opens in an onboarding course can demand all five in a compliance one, with no change to the accordion.
Keep the best attempt, keep only the first, pay less for each retry, give half marks for an answer that is one step off. Retries still report, so a learner who kept going does not look like one who walked away.
Reopen one block, one subsection, or a whole chapter — and filter by the kind of block, so a rewind can hit the exercises and leave the reading alone. Each reset reports how much it actually undid.
A learner moved without explanation assumes the course is broken. The preview shows the banner they will see, and the wording is yours: say what to review, not that they failed.
Answers changed before submitting. Rows reopened. Attempts before a pass. Blocks where progress stops and never resumes. The matching block reports how much reworking a learner needed, which is a different question from whether they got it right.
Export to H5P and every statement arrives with its verb and result. Export to SCORM and the bridge collapses it to one score and one status, because that is all SCORM defines. Knowing which you are shipping into changes what is worth measuring.
Look and feel
The theme designer sets brand colours, type, corners, spacing and a course logo. Every themed piece carries a class, so chapter titles, callouts, cards, tab headers, buttons, progress bars and feedback states move together — including the code editor a learner types into, which takes its own theme so a dark course does not end up with a white editor in the middle of it.
The five below are illustrations rather than presets, built to show how far the theme layer moves. Press one and watch both panes change together.
PETG needs a hotter nozzle than PLA. Below 230°C it will not bond, and the rig refuses to start rather than laying down a part that comes apart in the hand.
// three refusals, then send them back state.fails = (state.fails || 0) + 1; if (!statement.pass && state.fails >= 3) { course.resetChapter('materials', { type: 'exercise' }); course.goTo('materials'); state.fails = 0; }
These five are illustrations, not shipped presets — you build your own in the theme designer, from colours, type, corners and spacing. The editor themes are real: forge-light is the default, with paper, slate, midnight and contrast alongside it.
Custom interactions
A Custom HTML block takes markup, styles and script and runs them in a sealed frame. This is where a model-generated interaction goes: paste it in, give it the four calls it needs to report back, and it becomes a gradeable part of the course rather than a decoration.
The printer in the built-in course is one of these. It reports material selection, temperature changes, a refused start and a finished print as separate statements, which is why a rule could tell the difference between someone who is stuck and someone who is experimenting.
forge.complete() · forge.score(raw, max) · forge.emit(verb, result) · forge.resize(). That is the entire surface.For front-end developers
The surface area
01 · Override framework
Every component ships a default statement. A callout reports
experienced when it is seen, a question reports
answered with a score. An override replaces that default with
your own code, written in Monaco with autocomplete for the whole scope, and
attached to the block rather than forked into it. The same accordion can
complete on one interaction in one course and only after every row is read
in another.
emit, state, statement, props. State persists for the life of the block, so attempts and partial progress are countable.course.get(), uncomplete(), reset(), goTo(), resetSection(), resetChapter() — addressed by ref, so inserting a chapter never breaks a pointer.02 · Custom HTML
A Custom HTML block runs your fragment in its own frame with a unique
opaque origin. It cannot read the page around it, touch storage or make
network calls, and it reports back through one object:
forge.complete(), forge.score(),
forge.emit(), forge.resize(). A machine
simulation can report material choice, a refused start and a finished run
as three separate statements, which is detail no host could have inferred
from outside.
The frame receives the theme as custom properties, and every themed element carries a class you can style globally.
The theme layer
A theme is a token set plus element and component overrides, adopted through a
constructable stylesheet so a component that mounts mid-session is correct on
its first paint rather than after the next panel touch. Code surfaces carry
their own palette — the exercise editor, the read-only samples and the code
frame follow the theme's editor tokens instead of a hard-coded white. Editor
themes are forge-light by default, with paper,
slate, midnight and contrast.
Values are sanitised on the way in: url(),
expression() and @import are rejected, and
structural properties — position, display,
z-index, overflow, width — are absent
from the allowlist entirely, so a theme cannot hide navigation or collapse
the page.
03 · Code exercises
A code exercise is a Monaco editor with a Validate action. Match against up to twenty accepted answers, add requirements that must appear regardless of shape, or post the submission to your own test service and score whatever comes back. Submitted code is never executed in the course.
For front-end developers
Documentation where you are typing
Each component declares a schema. That schema builds the properties panel and
feeds autocomplete inside the override editor, so props. lists
what this block actually has, with types, rather than what you remember from
the last one.
Block, ComponentKey, ProgressionConfig, AnimationConfig, XAPIStatement and the canvas message union are all declared, so an editor can tell you what a field accepts before you save.Where the code runs
Authoring is local. Untrusted markup is contained by construction rather than by a setting someone can forget to switch on.
Projects, assets, drafts and snapshots are files on your disk. Authoring works with the network off — on a plane, on an air-gapped review laptop, in a room where the content cannot leave. The network is needed for the licence check and for compilation, and what crosses it is project data you chose to export, not a mounted workspace.
The design canvas shows Custom HTML as inert source — no iframe, no Blob URL, no postMessage. Preview runs it for real in the sandbox. The exported package emits that same sandbox. Tests assert containment on both live surfaces and assert the canvas frames nothing.
Each block loads in its own frame with a unique opaque origin.
allow-same-origin is never granted beside
allow-scripts, the CSP withholds connect-src so a
block cannot phone anywhere, and message source windows are checked before a
statement is believed. A pasted fragment cannot read your page, your storage
or your cookies.
A code exercise matches a submission against accepted answers, checks it against your requirements, or posts it to a validation service you control. Nothing runs learner code in the course, so a clever answer is a marking problem rather than an execution risk.
The inline HTML sanitiser takes 24 XSS payloads in the suite — scheme
obfuscation, control-character smuggling, style-based exfiltration, handler
casing — and legitimate formatting has to survive them. Theme values get
their own: url(), expression() and
@import are rejected, and structural properties are not in the
allowlist at all.
An author editing their own project already controls it, so there is no escalation to prevent. What the runtime contains is error: a fixed four-name scope, a verb allowlist enforced at the bridge so a typo cannot reach the LMS as a novel activity type, and a throwing override that reports nothing rather than taking the block down.
A .forge5p is canonical JSON, deflated and base64url-encoded
inside a delimited text envelope, and checksummed — so corruption and
truncation surface on open instead of half-loading. That is obfuscation, not
encryption, and the API says so in as many words: two lines will decode and
inflate it. When confidentiality is the actual requirement,
serializeEncrypted() gives AES-256-GCM with a PBKDF2-derived
key, a fresh salt and nonce per export, and a wrong passphrase that fails to
decrypt rather than yielding altered content.
Client code cannot be made secret, so the client is treated as public and
the valuable step — compilation and SCORM packaging — belongs on the server,
which is also where an unlicensed copy stops being able to ship a package.
Today the compiler still ships in the installer and /v1/compile
is not deployed, so this is the shape being built rather than a boundary
already holding. Activation and licence validation are live: the server signs
a short-lived token, the client verifies it offline against an embedded public
key, and the private key never leaves the server.
When the endpoint lands it takes arbitrary author-supplied HTML and must never evaluate it; payload caps and per-licence rate limits are part of that work, not something already enforcing.
Built in · not a PDF
Ten chapters, authored in F4xAPI Studio and opened from the project list. It is the thing it teaches: it gates on the work you do, marks what you write, sends you back when you have not got it, and ends with the printer from the story above. Learn the tool by being a learner in it.
The library
Each block carries a ref, a progression rule, an entrance animation and an optional override. Tabs can hold other blocks, one level deep, which is enough for layout and not enough to lose track of.
Before it ships
Preview is the learner view under the constraints the export will meet, in a desktop, tablet or phone frame. Keep the progress from your last run while you edit, or switch on review mode to walk a long course end to end with every gate bypassed. Review mode is not a field on a block, is never written to a project file and the compiler has never heard of it, so no course can ship with its gates open.
.forge5p text file per project: content, theme and embedded assets in a delimited envelope, opened and saved locally..forge5p as readable by anyone holding it.serializeEncrypted() is AES-256-GCM with a PBKDF2-derived key, a fresh salt and nonce per export, and an auth tag — for handing a file to someone who should not be able to read it. Don't version-control that form: the random salt and IV mean the same project encrypts to different bytes every time, and the ciphertext will not compress, so the repo accumulates opaque blobs with no diff, no merge and no blame. Commit the plain archive and keep the repository private.Export
The package is a zip of content and code in an open format. Anything that supports H5P can serve, edit or extract it, and nothing downstream needs this editor.
Moodle and anything else that reads H5P receives every statement with its verb and result, per-block scores kept separate, completion for each block and for the course, and whatever your overrides emitted. A report can name the block a learner stalled on.
A bridge translates the course's own xAPI into CMI calls. SCORM has a fixed vocabulary, so per-block detail collapses into a rolled-up score, a completion status and a pass or fail. Use it when the LMS cannot take H5P, not by preference.
Getting started, writing an xAPI override, custom HTML blocks and code exercises, opened from the toolbar. The worked examples are run by the test suite rather than proofread, because an example that has drifted out of correctness is worse than none.
Who this is for
You already argue about what completion should mean. Here that argument has somewhere to live: a short rule attached to a block, separate from the content, different for the client who wants it different. You can read a model-generated interaction well enough to wire it up, and you want the course to answer back while a learner is still in it.
Monaco, an open package format, sandboxed opaque-origin frames, a theme layer built on custom properties and classes, canonical serialisation that makes an unchanged project export byte for byte, a single text file that commits and merges as text, and override parsing that runs before export. Course tooling that behaves like tooling.
If your courses are readings and quizzes, this is overhead with no return. It earns its keep when the training needs an interface that does not exist yet, and needs that interface to be assessed rather than watched.
Pilot phase
F4xAPI Studio is in pilot. The Windows build is the trial version: author, preview, theme, write overrides and work through the built-in course. Export needs a licence. Tell us what breaks and what is missing.
Download the x64 Windows installer