Ground & Form F4xAPIStudio Pilot

Pilot build · Windows x64

Build the course.
Then decide what it means.

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.

Build the course.
Then decide what it reports.

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

Authoring
EditPreview Verify · Export
Chapters
</>
{;}
Canvas
Properties
xAPI behaviour
override · fb-code · escalation-quiz
// Three failures — send them back through the exercises only. state.fails = (state.fails || 0) + 1; if (!statement.pass && state.fails >= 3) { course.resetChapter('patterns', { type: 'fb-code' }); course.goTo('patterns'); state.fails = 0; } emit('answered', { result: { score: statement.score, completion: statement.pass, } });
Preview
Patterns
Try it
Continue

The problem this exists for

You finished the course.
Is it any good?

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.

Statements, live
chapter 6 · one learner · one run
What it exposes
Attempts before pass11
Answers changed7
Reopened glossary
Sent back by an overrideonce

For learning designers

A loop, watched from the outside

Someone is failing at the printer. Watch what the course does about it.

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.

FormaJet rig · learner view
FormaJet · bed 60°C
Material
PLA PETG ABS
Nozzle
190°C PETG needs 230
0230 minimum300
Idle. Choose a material, set the nozzle, start the print.
Start print
What the course is told
Where the learner stands
Materials · the readingDone
Materials · exercise 1Done
Materials · exercise 2Done
Capstone · the rigHere
01 / 06

The commission. The machine works. Nothing it does reaches the course, so nothing the learner does is worth anything yet.

SCORM 1.2 export
What the LMS can store: one score, one status, pass or fail.
H5P export · your own xAPI
What the same run looks like when nothing has to be collapsed.
Both columns are the same learner, the same four attempts. SCORM has a fixed vocabulary, so the bridge rolls everything into the values it defines — which is why the left column cannot tell you that the rewind happened, or that the first three failures were all the same mistake.

The rule that sent them back is six lines.

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.

printer-rig · xAPI behaviour
state.fails = (state.fails || 0) + 1; if (!statement.pass && state.fails >= 3) { course.resetChapter('materials', { type: 'exercise' }); course.goTo('materials'); state.fails = 0; }

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

Things every course quietly decides for you. Here you decide them.

What counts as done

Seen, or understood?

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.

What an attempt is worth

Full credit, or fading credit

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.

Where someone goes next

Back a page, or back a chapter

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.

What they are told about it

A reason, not a bounce

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.

What you find out

Attempts, changes, stalls

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.

What survives the LMS

Everything, or one number

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

One theme, and everything follows it.

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.

Learner viewGround & Form
Chapter 3 · Materials

Choosing a material

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.

TipSet the nozzle before the bed. The bed follows the material; the nozzle decides whether it prints at all.
Continue✓ Answer correct
printer-rig.js forge-light
1
2
3
4
5
6
7
8
9
// 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;
}
JavaScriptLn 4, Col 1 Monaco · forge-light

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

If your assistant can write it, the course can run it.

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.

  • To reportforge.complete() · forge.score(raw, max) · forge.emit(verb, result) · forge.resize(). That is the entire surface.
  • ContainmentThe frame cannot read your page, touch storage or reach the network, so a pasted fragment cannot do damage you did not intend.
  • PreviewIt runs for real in Preview under the same constraints it will meet in the LMS, on a desktop, tablet or phone frame.
Custom HTML · the four calls
// when the print finishes forge.emit('completed', { score: { raw: quality, max: 100 }, completion: true }); // when the machine refuses to start forge.emit('progressed', { completion: false });
Inherited from the theme
--brand-primary --page-bg --brand-accent --surface --text-primary --surface-border --font-title --radius --font-body --content-width

For front-end developers

The surface area

Control of the signal, and of the experience that produces it.

01 · Override framework

A few lines of JavaScript decide what a block means.

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.

  • Scopeemit, state, statement, props. State persists for the life of the block, so attempts and partial progress are countable.
  • Verbsexperienced, interacted, answered, completed, progressed. A typo is dropped at the bridge rather than reaching the gradebook as a novel activity type.
  • Reachcourse.get(), uncomplete(), reset(), goTo(), resetSection(), resetChapter() — addressed by ref, so inserting a chapter never breaks a pointer.
  • FaultsA throwing override reports nothing instead of taking the block down. Statements emitted before the fault are kept, and verification parses every override before export.
Properties · xAPI behaviour
fb-accordion · glossary
// Require every row, not just one. state.viewed = state.viewed || []; if (!state.viewed.includes(statement.itemIndex)) state.viewed.push(statement.itemIndex); const total = props.items.length; const seen = state.viewed.length; emit('progressed', { result: { score: { raw: seen, max: total, scaled: seen / total }, completion: seen === total, } });
Progression
view_required80% visible for 2s
interactionany completion result
score_thresholdscaled ≥ your mark
all_itemsone per sub-item
customyour predicate

02 · Custom HTML

Write the interface the training actually needs.

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.

Theme classes
.thm-chapter-title .thm-callout .thm-body .thm-card .thm-button .thm-tab .thm-continue .thm-question .thm-progress .thm-toc-item .thm-answer-correct .thm-feedback-pass .thm-code-frame .thm-feedback-fail
--brand-primary --page-bg --radius --brand-accent --surface --font-title --text-primary --surface-border --font-body

The theme layer

Tokens out, classes over the top, editor included.

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

Ask for code, and mark it.

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.

  • LanguagesJavaScript, TypeScript, Java, C#, C++, C, Python, SysML, SQL, HTML, CSS, JSON, shell, plain text.
  • MarkingAnswer matching that ignores formatting, extra requirements with their own failure messages, or a validation service over HTTPS.
  • AttemptsCap them, reveal the answer when they run out, or write an override that pays less credit for each retry and reports the shape of the struggle.
  • CompanionA read-only Code Sample block with line numbers and emphasis, for showing a technique before you ask for it.
Code exercise
javascript · attempt 2 of 5
1 // Report a score built from state and props. 2 const seen = state.viewed.length; 3 emit('progressed', { result: { 4 score: { raw: seen, max: props.items.length }, 5 } });
Validate requirement: must use props
Result
✓ matches accepted answer 3
✓ uses props
✕ no max supplied on the score
scaled 0.66 · credit 0.8 on attempt 2

For front-end developers

Documentation where you are typing

Every prop a block takes, in the editor with you.

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.

xAPI behaviour fb-matching
// scope: emit · state · statement · props · course
const total = props.
JavaScript Monaco · forge-light
Course API
course.get(ref) // { complete, scaled, attempts, title } course.uncomplete(ref) // close the gate, keep the score course.reset(ref) // clear it entirely course.goTo(ref) // move the learner course.refs() // every ref in the course course.resetTo(ref) // that block and everything after course.resetSection(ref) // one subsection course.resetChapter(ref, { type })
  • In appFour topics from the toolbar: getting started, writing an xAPI override, custom HTML blocks, code exercises. Each with runnable examples.
  • Tested docsThe worked examples are executed by the test suite — the abacus arithmetic, the tic-tac-toe win detection, the per-turn reporting — and every code block in the reference is syntax-checked. An example that has drifted is worse than none.
  • Typed modelBlock, 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.
  • Service contractCode exercises can post a submission to your own endpoint over HTTPS and score the response. The request and response shapes are documented and stable.

Where the code runs

A desktop tool, and three sandboxes it never lets you skip.

Authoring is local. Untrusted markup is contained by construction rather than by a setting someone can forget to switch on.

Local by default

Your projects stay on your machine

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.

Three surfaces, three treatments

The canvas never runs your markup

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.

The sandbox contract

Opaque origin, no way home

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.

Learner code

Submissions are never executed

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.

Sanitisers under attack

Two of them, tested adversarially

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.

Overrides contain mistakes

Not a privilege boundary, and not sold as one

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.

Files that fail closed

Opaque at a glance, honest about it

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.

The compile boundary

The design, and where it has got to

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

The tool ships with the course that teaches it.

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.

01
The pieces
Chapters, subsections, blocks, and what a gate is.
02
How reporting works
Verbs, results, and why the verb alone changes nothing.
03
Wiring your first gate
Progression types against real content.
04
When a gate misfires
Four broken courses, four faults to find.
05
Choosing between designs
No correct answer, only a defensible one.
06
One statement at a time
Reading an override before writing one.
07
Patterns worth knowing
Count items, keep the best attempt, decay by attempt, progress without completion.
08
Repair, then finish
Fixing overrides that report the wrong thing.
09
Sending a learner back
Remediation by ref, without punishing the reading.
10
Capstone — wire up the printer
A running machine with no reporting. Make it report.

The library

Fifteen blocks, every one of them addressable.

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.

Layout
Accordion
Expandable sections. Report on one row or on all of them.
Layout
Tabs
Tabbed panels, and the only block that can contain others.
Layout
Timeline
Dated entries, vertical or horizontal.
Layout
Card Grid
Flip or click-to-open cards, two to four columns.
Layout
Callout
Info, tip, warning, danger, success. Each one themed separately.
Layout
Divider
A rule, optionally labelled.
Media
Image Block
Paired text and image, four layouts.
Media
</>
Code Sample
Read-only highlighted code with line emphasis.
Interaction
Matching
Pairs, with distractors, penalties per change, and a report on how much reworking it took.
Interaction
Question Check
Single answer, immediate feedback, retry rules.
Interaction
Reveal
Content held back until the learner asks for it.
Interaction
{;}
Code Exercise
Monaco, twenty accepted answers, requirements, or your own test service.
Layout
</>
Custom HTML
Sandboxed markup for the interface no stock block covers.
Feedback
Star Rating
Renders a score you supply, animated.
Feedback
Progress Bar
Themed progress against any value you compute.

Before it ships

Check it as a learner, then check it as a machine.

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.

  • VerificationTwo stages run today, both local and fast: schema and structure, then a parse of every override with the verb allowlist checked. A third stage — Playwright runtime verification — is scaffolded and currently passes everything, so read it as a placeholder rather than a gate.
  • SnapshotsRollback points stored beside the project, browsable and restorable.
  • Project fileOne .forge5p text file per project: content, theme and embedded assets in a delimited envelope, opened and saved locally.
  • Version controlThe format was built for it. Serialisation is canonical — object keys sorted, arrays ordered by explicit position, no timestamps in the checksummed payload — and volatile metadata sits in the envelope header, outside the body. The same project produces byte-identical output every time, so a commit appears only when the project actually changed. It is a single text file, so it commits, diffs and merges as text; the diff is base64, so you see that something changed rather than a readable line-by-line of what.
  • Not encryptionThe default envelope is canonical JSON, deflated, then base64url-encoded. That is obfuscation: it stops anyone hand-editing the file and makes corruption fail a checksum rather than half-load, but two lines will decode and inflate it. Treat a plain .forge5p as readable by anyone holding it.
  • Encrypted archivesserializeEncrypted() 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.
Verification
38 blocks · 2 stages live
✓ stage 1 · structure and refs
✓ stage 2 · overrides parse, verbs allowed
! chapter 4 · gate can never open
score_threshold 0.9, max score 0.75
· stage 3 · runtime pass, scaffolded
Devices
Desktop · Tablet · Phone frames
Preserve learner progress across edits
Review mode, gates bypassed
Entrance animations, reduced-motion aware

Export

H5P where you can have it, SCORM where you cannot.

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.

.h5p

Full detail, per block

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.

SCORM 1.2 · 2004

One score, honestly labelled

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.

Documentation

In the app, beside the work

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

Two people, usually. Sometimes the same person on different days.

Instructional and learning experience designers

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.

Read the designer's route →

Front-end developers and designers

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.

Read the developer's route →

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

Try it on a real course.

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
Windows 10 / 11 · 64-bit Pilot build · trial licence Offline authoring
Windows only for the moment. macOS and Linux builds exist in the same codebase and are not being distributed during the pilot, because nobody has signed and tested them properly yet. Ask if you need one and we will tell you honestly where it stands.