All skills
dmccreary avatar

/add-xapi-events-to-microsim

@d493f4f
by Dan McCrearydmccreary/claude-skills104 stars
16

Use when the user wants an existing MicroSim or chapter quiz to record what students do with it. Typical requests are "add xAPI events", "add tracking or analytics", "make this sim feed the LRS or LRS-Lite", "instrument chapter 5's sims", or "what should this sim record?". Use it even if they never say "xAPI". Not for building a new MicroSim (use microsim-generator) or for LRS backend work.

  • 27 files
  • 234.6 KB
  • Updated 5 days ago
  • GitHub

Use this Skill: https://skilld.dev/gh/dmccreary/claude-skills/add-xapi-events-to-microsim

This session only. Nothing lands on disk.

SKILL.md

โ‰ˆ106 tokens always: the name and description. โ‰ˆ5.5k when used: this file. โ‰ˆ29k more on demand in 16 files.

Add xAPI Events to a MicroSim

Version: 0.2 (2026-09-26). This version folds in the lessons of the first whole-book batch, the 20 sims of eight-hour-entrepreneur.

What this skill does

This skill takes a MicroSim that already works and makes it report what the student did, as xAPI statements, without changing what the sim does. The per-sim code is a thin mapping, usually 20 to 60 lines, onto a shared runtime. It is never a copy of the runtime.

The runtime decides Full vs. Compact from config and builds every statement. It also enforces the producer contract and renders the teaching panel. Your job is to decide which interactions are evidence, of what, and for which concept, and then wire them up.

The canonical runtime and the four reference sims live in the learning-record-store repo. Read them whenever this skill points at one; they are the ground truth.

LRS_REPO=~/Documents/ws/learning-record-store
$LRS_REPO/docs/js/lrs-sim.js           the API a sim calls (its header comment is the contract)
$LRS_REPO/docs/js/lrs-config.js        per-book identity + default policy (example)
$LRS_REPO/docs/sims/bouncing-ball/     p5, slider + Start/Pause            (verified)
$LRS_REPO/docs/sims/sine-wave/         p5, three sliders                   (verified)
$LRS_REPO/docs/sims/scientific-method/ Mermaid, hover + pin, page dwell    (verified)
$LRS_REPO/docs/sims/animal-cell/       image overlay, explore + quiz       (verified)
$LRS_REPO/docs/js/quiz-xapi.js         chapter quiz page, answers only     (verified)
$LRS_REPO/docs/specs/xapi-producer-contract-v1.md   the statement rules

Scripts are in this skill's scripts/ directory (SKILL_DIR below). The Python scripts need only the standard library, except the two that drive a browser. Run those with uv run --with playwright==1.58.0 python ....

Principles, and why they matter

  • Instrument the act the sim was designed for. Don't record every act you can detect. The sim's own instructions tell you which act was designed ("Click a node to see details", "Hover over a marker or a label"). Hover is absent on touch devices, so hover-only evidence is biased by device, and device correlates with school funding. Record hover only when it is the designed act, or when click is simply how a touchscreen does the same thing (then they are one act; see animal-cell).
  • Not every interaction is evidence. Mouse crossings (<600 ms hover), mis-clicks (<250 ms runs), glances (<1 s page dwell), and events the program fires on itself (autosize, setView, rangechange) are noise. If you emit them, dashboards will believe them.
  • Answers are never folded. A checked answer (a quiz item, a checked prediction, a goal reached) is its own answered statement in both modes, emitted as it happens. Only exposure evidence folds into the Compact summary. BKT reads the order of attempts.
  • Mode and teaching UI come from config, never from the sim. The book's docs/js/lrs-config.js sets the default for every sim. A sim's metadata.json xapi block overrides it for that one sim. Production sims show nothing.
  • Any instrumented sim can become a teaching aid for one visit. A reader who adds ?xapi=teaching to the page that embeds the sim sees its statement log, with no file edited. So a production sim's layout must still work with the panel showing. The runtime grows the iframe to fit the panel, but a layout that fills its frame (100vh, or html/body at 100%) defeats that. Pin such a layout while the panel is present.
  • Never edit the runtime or a vendored library. docs/js/lrs-*.js is identical in every book. A vendored file such as shared-libs/diagram.js is shared by many sims. Wrap functions and add listeners beside the originals instead. If the runtime truly lacks something, use lrs.emit(spec), or stop and tell the user.
  • The sim must still run without the runtime. Teachers paste p5 sketches into the p5.js editor. Guard every call site with if (window.LRSSim) or a null lrs. scripts/check-no-runtime.py proves it (step 9).

Workflow

Work through these steps in order. Steps 2โ€“4 are the thinking. Show the user the evidence table from step 3 before you write code whenever there is anything debatable, for example two input paths to one act, a canvas hit-test, or no matching concept.

Step 0 โ€” Locate the book and the sim

The book root is the directory containing mkdocs.yml. The sim is docs/sims/<sim-name>/, containing main.html (the iframe payload), index.md (the lesson page that embeds it), and usually metadata.json. For a chapter quiz, the "sim" is the rendered quiz page; go straight to references/adapters/quiz-page.md.

For a batch request ("instrument chapter 5's sims"), handle one sim at a time and keep a running table of results. Finish and verify each sim before starting the next.

A whole book at once (proven on the 20 sims of eight-hour-entrepreneur, 2026-09-26) can use parallel subagents, one per group of sims that share a library or template. Split the work this way:

  • The coordinator does the per-book steps, once:
    • install-runtime.py (step 5), before any worker starts.
    • One sim wired and verified by hand, as the in-book reference the workers copy.
    • At the end: sync-status.py --apply (step 7), check-no-runtime.py over the whole book, mkdocs build --strict, and a fresh check-xapi.py run of every sim with its actions file.
  • Workers do steps 1โ€“4, 6, the metadata half of step 7, and step 9 for their own sims only. They must not touch mkdocs.yml, extra.css, docs/js/, chapter files, status: or git. check-xapi.py and measure-iframe.py each serve on a random port, so they run safely side by side.
  • Relay findings between workers. One worker's discovery often applies to another's sims. For example, the keyPressed panel guard in p5-canvas.md was found by one worker and needed by two others.

Step 1 โ€” Detect the library (from script tags, not the catalog)

python3 $SKILL_DIR/scripts/detect-library.py docs/sims/<sim-name>

The catalog's library field is empty for about 45% of sims, so trust the <script> tags. The report also lists:

  • whether the runtime is already loaded (if so, this is a revision, not a first pass),
  • vendored shared files you must wrap rather than edit,
  • a first-pass inventory of interaction hooks found in the JS.

Open the matching adapter and read it before going further:

Library / shape Adapter Status
p5.js with createSlider/createButton/createSelect/โ€ฆ references/adapters/p5-dom-controls.md verified
p5.js with canvas mousePressed/mouseDragged hit-tests references/adapters/p5-canvas.md click/predict and click/step/keyboard explorers verified; canvas hover piloted; drags unverified
Mermaid flowchart (click-to-pin or hover infobox) references/adapters/mermaid-html.md verified
Image with hotspot markers/labels (image-overlay, diagram.js) references/adapters/image-overlay.md verified
Chapter quiz page (??? question answers) references/adapters/quiz-page.md verified
vis-network references/adapters/vis-network.md unverified
vis-timeline references/adapters/vis-timeline.md item click/hover/step verified; zoom/pan unverified
Chart.js references/adapters/chartjs.md verified
Plotly references/adapters/plotly.md unverified
Leaflet references/adapters/leaflet.md unverified
Plain HTML/SVG controls references/adapters/mermaid-html.md (same DOM-listener pattern) verified

Many sims mix shapes, for example p5 DOM controls plus canvas-drawn buttons. Read every adapter that applies. An unverified adapter was drafted from library docs and has not passed a pilot yet. Follow it, but verify its claims against the real sim and tell the user which parts you had to correct. A piloted adapter passed a pilot in an eval run, and its corrections are folded in. It becomes verified once that pilot sim is committed to its book.

Step 2 โ€” Inventory the interactions

Read the sim's JS and HTML completely; the detector's inventory is only a starting point. List every way the student can change or inspect the sim: each control, each clickable thing, each hover target, each mode switch, each check of an answer. Also note the sim's own instruction text, because it names the designed act.

Step 3 โ€” Classify each interaction into an evidence class

Read references/evidence-classes.md. Every interaction becomes exactly one of:

# Class Handle Typical source
1 Continuous parameter lrs.slider(key, o).input(v) / .settle(v) slider, zoom, numeric input
2 Discrete inspection lrs.item(key, o).study(mode, ms) click, hover โ‰ฅ 600 ms, pin, select, legend toggle
3 Run/Pause lrs.button(key).press(action) + lrs.runner(o).start()/.stop(reason) Start/Pause, Play/Stop
3a Discrete press lrs.button(key, o).press(action) Reset, Randomize, scale toggle
4 Page dwell pageDwell: true in LRSSim.create any sim with no Run control
5 Assessment lrs.question(key, o).answer({success, response, โ€ฆ}) quiz item, checked prediction, goal
6 Focus loss handled by the runtime tab hidden, scroll-away, idle, blur
โ€” Not evidence nothing sub-threshold hover, programmatic events, pure layout

Write the result as a table for the user: interaction โ†’ class โ†’ fragment key โ†’ concept. Name each fragment key for what the thing is (#frequency-slider, #start-pause-control, #q-kafka), never for its position.

Step 4 โ€” Map each object to a concept id

Read references/concept-mapping.md. In short: the concept id is namespaced as {conceptPrefix}-{ConceptID} from the book's docs/learning-graph/learning-graph.csv. Each statement carries one concept. To propose candidates, run:

python3 $SKILL_DIR/scripts/find-concepts.py --book . --sim docs/sims/<sim-name>
python3 $SKILL_DIR/scripts/find-concepts.py --book . "prediction" "data loss"

The script only proposes; you choose. If nothing genuinely matches, leave that object unmapped and tell the user. Don't guess the nearest label. A wrong concept id is worse than none, because it silently credits mastery of the wrong thing. A mapping the book itself states is not a guess. If the chapter or a sibling sim's spec pairs Gateway with Kafka Unavailable Failure, use that pairing and cite it.

While mapping a question, check its answer key against the chapter. A key that contradicts the text makes every student who learned the chapter score success: false. Both chaos-kill eval runs found such a key. Report it; changing it is a content decision for the user.

In code, write LRS.conceptId(353) so the prefix comes from the book's config. Record the map, with full ids, in metadata.json (step 7).

Step 5 โ€” Confirm the runtime is installed in this book

python3 $SKILL_DIR/scripts/install-runtime.py --book . --check

If files are missing, run it without --check. It copies lrs-xapi.js, lrs-lite-sim.js, lrs-sim.js, xapi-json-viewer.js and lrs-xapi.css from the canonical repo. It writes docs/js/lrs-config.js from mkdocs.yml only if that file is absent, because the config is the book's identity and is never overwritten. If the script reports drift (the book's copy differs from canonical), stop and tell the user; don't pass --force on your own. Phase 3 will move this job into book-installer.

Step 6 โ€” Wire the adapter

  1. main.html: add the script block from assets/main-html-script-block.html, in that order, before the sim's own script. Add the CSS link. For layouts where the panel should sit below the sim, add <div id="xapi-slot"></div> after <main>.
  2. The sim's JS: create one LRSSim instance, then one handle per object from step 3, then call the handles from the sim's existing event handlers. Follow the adapter file for the library. See references/runtime-api.md for every option, with a worked example of each class.
  3. Keep the sim's original behaviour byte-for-byte where you can: add listeners, wrap functions, and don't reorder the sim's own logic. For a sim whose main file is a vendored library, put the instrumentation in a separate xapi.js (animal-cell).
  4. Clicks and mouse moves inside the xAPI panel must not reach the sim's own handlers, such as "click outside to unpin" or a follow-the-mouse infobox. Check for these.
  5. Make the layout tolerate the panel even for a production sim, because ?xapi=teaching can switch it on. Give the panel a place to go (mount). If the sim fills its frame, add teaching-only CSS scoped to body:has(> .xapi-panel) that pins the container to its production pixel height. See the Mermaid adapter's "Layout" section. That CSS costs nothing in production, where no panel exists.

Read references/pitfalls.md before you finish this step; every item on it was learned by shipping the bug.

Step 7 โ€” Set the metadata.json xapi block

"xapi": {
  "concept": "learning-record-store-353",
  "objects": {
    "service-select": "learning-record-store-353",
    "q-kafka": "learning-record-store-334",
    "q-neo4j": "learning-record-store-336"
  }
}

In this example, the page and its generic controls take the sim's concept (353, Chaos Kill Test). Each question takes the concept it tests (334, Kafka Unavailable Failure).

  • concept is the page-level concept (runs, page dwell, the Compact summary). objects maps every fragment key to its concept id. check-xapi.py verifies that the emitted statements match this map, so the map and the code can't drift apart silently. The map also gives the MicroSim catalog exact concept ids, which it lacks today.
  • Production sims (the normal case) carry no policy keys, so they inherit the book's lrs-config.js default. If the book's default is ever teaching: true, add "teaching": false to keep this sim silent.
  • Teaching sims (ones that exist to teach xAPI) add "compact": false, "teaching": true, so they start on Full and show the log.
  • Policy keys and their meaning: compact, teaching, idleMs, offscreenMs, blurMs. The precedence, lowest first, is: runtime defaults < book lrs-config.js xapi < page policy option < this block < the URL switch ?xapi= (one visit only).

Then mark the sim in the nav (always, for every sim this skill instruments):

python3 $SKILL_DIR/scripts/sync-status.py --book . --apply

This sets status: instrumented in the index.md frontmatter of every sim whose main.html loads the runtime and whose code calls LRSSim.create. It marks that the capability is present, whether or not teaching is on. The nav shows the signal icon (the Material Design Icons "access-point" broadcast symbol, in teal). That icon is the standard for xAPI instrumentation in every book (Dan, 2026-09-26), so don't substitute another. If a book lacks the icon, --apply installs it by appending assets/status-instrumented.css to docs/css/extra.css and adding the tooltip under mkdocs.yml extra.status. The script never overwrites an approved sign-off.

If the book's AGENTS.md (or CLAUDE.md) lists the allowed status: values, usually scaffold | built | approved, add instrumented to that list, along with the comment above extra.status in mkdocs.yml. Otherwise the next agent that follows those rules may "fix" the new status back to built.

Step 8 โ€” Re-measure the iframe height

uv run --with playwright==1.58.0 python $SKILL_DIR/scripts/measure-iframe.py docs/sims/<sim-name>

A teaching sim gains a panel, so its iframe must grow. The script measures a full log at 375, 700 and 900 px and recommends a height from the 700 px measurement.

Size for the laptop column, not the phone. On the published site the content column is 690โ€“760 px on laptops and tablets. About 90% of the target students use laptops, and phones are often banned in US junior-high and high schools. The ~20% mobile traffic in the logs is mostly browsing the text, not using the sims (Dan, 2026-09-26). A sim that is taller at 375 px may scroll or clip on a phone; that's accepted. Mention it in the report, but don't grow the iframe for it unless the user asks. It measures the state after load. If the sim is taller in another state (quiz mode, an open detail panel), put it there first with --eval "sim.setMode('quiz')". An empty-log screenshot is not a measurement.

The script also lists every iframe in the book that embeds this sim, not just the one in index.md. Chapters embed sims too, and the teaching panel appears wherever the sim is shown, because the mode comes from the sim's own metadata.json. It also reports the sim's CANVAS_HEIGHT source. microsim-utils' sync-iframe-heights.py sets every embedding iframe to CANVAS_HEIGHT + 2. So if you edit iframe heights by hand, its next run silently shrinks them back. Instead:

  1. Set the sim's CANVAS_HEIGHT (its // CANVAS_HEIGHT: comment, or canvasHeight in metadata.json, whichever the script reports) to the recommended height โˆ’ 2.
  2. Run python3 ~/Documents/ws/ibook-skills/src/microsim-utils/sync-iframe-heights.py --project-dir . --sim <sim-name> to update every embed.

A production sim should not change height; if it did, the wiring added something visible, so find it.

Step 9 โ€” Verify both modes headless

uv run --with playwright==1.58.0 python $SKILL_DIR/scripts/check-xapi.py docs/sims/<sim-name> \
    --actions docs/sims/<sim-name>/xapi-actions.json      # optional; see below

The script serves docs/ and embeds the sim in an iframe the way the book does. It rewrites metadata.json in flight to Full, then Compact, then production. It then loads the production sim once more with ?xapi=teaching, at its real index.md iframe height. In each mode it drives interactions and asserts the contract:

  • only the three verbs,
  • IRIs from the book's siteUrl, with no main.html and no localhost,
  • grouping present,
  • concept ids present and matching the metadata map,
  • Compact silent until focus loss, then exactly one summary with statements_represented,
  • answers passing through unfolded,
  • no teaching UI in production,
  • with ?xapi=teaching, the panel appears and fits its iframe,
  • a clean console.

Generic driving covers DOM sliders, selects, checkboxes and buttons. For canvas clicks, hovers, chart points or graph nodes, write a small actions file. The format is in scripts/check-xapi.py --help and in each adapter. Keep that file in the scratch space, not in the book, unless the user wants it. Iterate until every check passes. Report any warning you decide to accept, and why.

Some actions cover cases that are easy to miss:

  • type sends real keystrokes into a text box, then commits them.
  • "leave": false on a hover keeps the pointer in place, so a following click_at is the same visit.
  • Action selectors skip the teaching panel's own controls; add "panel": true to target them deliberately.

Drive every evidence class you wired. Full mode must show each one at least once, which is the positive control that makes the other checks mean something.

Then prove the sim still runs with no runtime at all (the p5.js-editor case). The script blocks the five runtime scripts, loads the sim, clicks its buttons, and fails on any uncaught error:

uv run --with playwright==1.58.0 python $SKILL_DIR/scripts/check-no-runtime.py --book . <sim-name> ...

With no sim names it checks every instrumented sim in the book.

Step 10 โ€” Lesson text (teaching sims only)

For a sim that teaches xAPI, add a section to index.md that lists what it emits in each mode. Also add keyQuestions in metadata.json that make the student compare the two streams. Use $LRS_REPO/docs/sims/bouncing-ball/index.md as the model. Production sims get no lesson text about xAPI.

Step 11 โ€” Report

Tell the user, briefly:

  • the evidence table (interaction โ†’ class โ†’ key โ†’ concept),
  • anything left unmapped and why,
  • files changed,
  • the check-xapi.py summary, with the iframe height before and after,
  • any unverified-adapter claim you had to correct. That feedback is how an adapter becomes verified.

Reference files

File Read it when
references/runtime-api.md Writing the wiring (step 6): every LRSSim option and handle, with worked examples
references/evidence-classes.md Classifying (step 3): thresholds, fragment naming, Full vs. Compact per class
references/concept-mapping.md Mapping concepts (step 4)
references/contract-digest.md Any doubt about a statement's shape; the rules the runtime enforces
references/pitfalls.md Before finishing step 6, and whenever a check fails for no obvious reason
references/adapters/*.md Step 1 onward, for the sim's library

Scripts

Script Purpose
scripts/detect-library.py Library from script tags, runtime status, vendored files, interaction inventory
scripts/find-concepts.py Candidate learning-graph concepts for a sim or search terms, with namespaced ids
scripts/install-runtime.py Check or install the runtime and lrs-config.js in a book
scripts/check-xapi.py Headless contract check of Full, Compact, production and ?xapi=teaching modes
scripts/measure-iframe.py Iframe height with a full log at 375/700/900 px, every embed, the CANVAS_HEIGHT source
scripts/sync-status.py status: instrumented for each instrumented sim, plus the signal-icon CSS and tooltip if missing
scripts/check-no-runtime.py Each sim still renders and runs, error-free, with the xAPI runtime blocked (the p5.js-editor case)

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at d493f4f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 17 hours ago.

Activeupdated 5 days ago
metadata
{
  "ibook.version": "0.2",
  "ibook.preferred-model": "opus"
}

README badge

README badge for dmccreary/claude-skills/add-xapi-events-to-microsim