# Embeducate — instructions for the AI building the app

Embeducate publishes a single, self-contained web app (HTML/CSS/JS) as a classroom activity — a shareable, embeddable link — and turns learner actions into a teacher's class report. Apps must **include the Embeducate SDK** to report learner activity — answers, progress, completion/score, and saved state — to populate this report. Instrument what the activity actually does — don't bolt a quiz/score/submit button onto an activity that isn't one.

**Keep the activity's UI neutral** — no Embeducate branding, "powered by" footers, "reported to Embeducate" messages, or platform mentions visible to learners. Embeducate adds its own chrome.

## Include the SDK

1. Add once, in `<head>`: `<script src="https://embeducate.com/sdk/v1.js"></script>`
2. **Report the grade — this is how a teacher sees any score at all.** Grading and report data only; omit `score`/`items` entirely for open-ended activities. `items[].attempts` is how many times the learner submitted an answer to *that* question (1 = answered once, no corrections). Do **not** include student work here — the platform takes it from your snapshot.
   - **`submit=self`** (activity has its own finish button): call `edu.signal.complete({ score, maxScore, items: [{ id, skill, label, type, value, display, correct, score, attempts, elementId }] })` on finish.
   - **`submit=host`** (platform shows Turn-in button): you are never called on finish, so register responder `edu.handlers.register("getResult", () => ({ score, maxScore, items: [...] }))`. **Without it a graded activity submits with no score and the report shows blanks.**
3. Declare in `<head>`:
   `<meta name="embeducate:task:title" content="…">`
   `<meta name="embeducate:submit" content="self">` — who submits: `self` (app) or `host` (platform Turn-in button). See "Who submits, and when".
   `<meta name="embeducate:sdk" content="0.1.0">` — SDK version built against.
   `<meta name="embeducate:build" content="…">` — set to `Date.now()` at build time.
   `<meta name="embeducate:lang" content="en">` — ISO 639-1 code(s), space-separated (e.g. `en he`).
   `<meta name="embeducate:dir" content="ltr">` — reading direction: `ltr` | `rtl` | `both`.
   *(host mode only)* `<meta name="embeducate:submit-policy" content="at-will">` — when Turn-in button is available: `at-will` (default) or `conditional`. See "Who submits, and when".
   *(recommended)* `<meta name="embeducate:viewmodes" content="read-only summary">` — supported review view modes (see "View modes").

**`complete` fields.** `complete` carries **grading and report data only** — never the learner's work. Work comes from your snapshot (see "State"), captured when you call `complete()`. No field exists for the whole submission: don't invent one.

`score`/`maxScore` are the grade. `items` is the per-question breakdown — each entry has a `value` (the answer), `correct`, `score`, `attempts` (how many times the learner submitted an answer to *that* question; `1` = answered once, no corrections), a `skill` tag, a short `label` (e.g. `"2x+5=32, x=?"`), and an optional `type` naming the answer's format: one of `numeric | choice | ordering | text | boolean | visual`. For open-ended/exploratory activities omit `score`/`items` entirely — call `complete()` with nothing and the snapshot alone is the submission. **Always give each answer a human-friendly `display` string** — the same answer written the way a teacher would read it (`"2/4"`, `"x = 8"`, `"True"`), never raw JSON; keep it one line, with full/structured data in `value`. Math **in `display`**: wrap in LaTeX delimiters — `\( … \)` inline, `\[ … \]` block (e.g. `"x = \\(\\frac{1}{2}\\)"`) — so the **report** renders it. This convention is *only* for `display`; render math in your **own activity UI** however you like (KaTeX, MathJax, images, plain text) — the platform requires no LaTeX or math library there. If an answer genuinely can't be shown as text — a drawing, a spatial arrangement — set `display: null`; the platform flags it and offers the visual submission view instead.

**Report every item in `items[]` with `edu.signal.answered(...)` when the learner answers** — see the signal list below. The report uses both: `items[]` is the final grading; signals are when each answer happened and what it looked like.

**When setting `display: null`, give the item an `elementId`** — the `id` of the element holding the answer, so the platform captures its picture for the teacher's report instead of a blank cell:

```js
{ id: "q2", label: "Draw the wave", type: "visual",
  value: { strokes }, display: null, elementId: "q2canvas" }   // <canvas id="q2canvas">
```

Require an `id` on a single element, not a class or selector. **Prefer a `<canvas>` or an `<svg>`**: these capture instantly on signal, making next-line screen replacement safe. Ordinary HTML rasterises a moment later; elements destroyed immediately after signalling may be missed — measured on a 3-question wizard replacing its screen at once: 3 of 3 captured for canvas, 1 of 3 for a `<div>`. That element must be **visible when the activity submits**, or on a screen the activity can restore from its own snapshot. Implement nothing else; the SDK handles capture.

## Who submits, and when

**Who — `embeducate:submit` (`self` | `host`):** Declare if your activity submits itself or relies on the platform's Turn-in button. Defaults to `host`; explicit declaration is best.

- **`self`** — **only** if the learner presses a button in *your* UI meaning "I am done with the whole thing". Not a Next or per-question Check button — a real finish. Call `edu.signal.complete(...)` **before** showing a results screen, summary, or anything replacing the current view — the snapshot taken then is what the teacher sees.
- **`host`** — the activity has **no** submit of its own (open reflection, sandbox, or quiz reusing the platform's button). The host shows a "Turn in" button; when pressed, the platform captures the snapshot itself. **You don't have to implement anything** — an activity with no handlers still turns in. **If you are unsure which mode you are in, choose `host`** — it always turns in, and a quiz with only per-question Check buttons and no final Submit is `host`, not `self`.

To include a graded activity score in the report, register a result responder:

  ```html
  <meta name="embeducate:submit" content="host">
  <script>
    edu.handlers.register("getResult", function () {
      var text = document.getElementById("answer").value.trim();
      return { score: text ? 1 : 0, maxScore: 1,
               items: [{ id: "reflection", skill: "writing", type: "text",
                         value: text, display: text, correct: !!text, score: text ? 1 : 0, attempts: 1 }] };
    });
  </script>
  ```

In `host` mode the SDK hides elements marked `data-edu-submit` so your button makes way for the host's.

**When — `embeducate:submit-policy` (`at-will` | `conditional`, host mode only):** By default, the Turn-in button is available **`at-will`**—the learner can turn in anytime; declare nothing. If submission requires a condition (e.g. *at least 3 examples attached*), declare **`conditional`** and drive runtime availability with `edu.signal.canSubmit(true|false)`: the button follows it, starting disabled until you first call `canSubmit(true)`.

**Learner identity is *not* your concern.** Don't collect names or emails—Embeducate identifies the student and records which student each submission belongs to. Your app only reports the answer and score.

## State: make it snapshot-able (important)

Build the activity to **snapshot its full state and rebuild from it** to enable resume, review and submission. Register **responders** with `edu.handlers.register(name, fn)`; the platform calls them and receives your plain, JSON-serializable return value.

### The state snapshot has three parts

```js
{ domain: {...}, interaction: {...}, ui: {...} }
```

- **`domain`** — learner actions and **where they are in the work**: submitted answers, attempts, hints used, and the **current step / page / question index**. Also unrecomputable data: an RNG **seed**, shuffle order, start timestamp.
- **`interaction`** — entered but **not committed**: draft text, option selected before pressing Check.
- **`ui`** — presentation atop the work: open results dialog, expanded panels, scroll.

**The learner's step is `domain`, not `ui`** — it's work, not decoration. `ui` is only chrome layered over it.

Don't store anything you can recompute (a score, a "Correct!" banner)—compute it on rebuild. Store a *dismissal* (`ui.feedbackDismissed`), because a learner's choice to close something can't be recomputed.

### The responders

- `edu.handlers.register("getState", fn)` — `fn` **returns** the three-part snapshot, which alone must rebuild
  the exact screen. Drawings can be raw strokes or base64 images.
- `edu.handlers.register("setState", fn)` — `fn` rebuilds the **entire view** from a previously returned snapshot. It must be **idempotent**: restoring an *earlier* snapshot has to reproduce that exact screen, so tear down anything a *later* state showed — results dialogs, end screens, overlays. (Common bug: it updates the answers but leaves a summary modal open, so scrubbing back in a teacher's review still shows the end screen.)
- `edu.handlers.register("reset", fn)` — return the activity to a blank slate (a fresh start / re-attempt).
- `edu.handlers.register("getResult", fn)` — *optional, graded activities only*: return `{ score, maxScore, items }`. Never learner's work (snapshot's job).
- `edu.handlers.register("screenshot", fn)` — *optional*: return the activity picture as the learner left it, as a data URI. **You usually do not need this** — the SDK captures a canvas directly or falls back to rasterising the whole page. Register a responder only to override this: to compose several canvases or render something offscreen. Return `{ image, width, height }` if the size is known, or just the data URI string.
  **Paint a background before you return it — never hand back transparent pixels.** `canvas.toDataURL()` on an unfilled canvas is fully transparent (a chart on a dark page appears white). First fill the canvas with the colour it sits on (`ctx.fillStyle = "#0f172a"; ctx.fillRect(...)` before drawing, or draw onto a filled offscreen canvas).
- `edu.state.sync()` — call when state changes so live watchers see it. We read state
  via your `getState` responder, preventing object handoffs and disagreements. It is
  **optional**: the platform captures state whenever it records (a `progress()` node,
  submission, host request), so forgetting costs a live view — never the learner's work.

**Call it BEFORE `edu.signal.progress()`, not after.** A `progress()` node stores the last synced state. Syncing afterwards leaves every node one interaction behind, collapsing two close changes into a single node showing both.

  ```js
  state.domain.answer = value;
  edu.state.sync();                    // first
  edu.signal.progress(done / total);   // then
  ```

**Every responder may be async** — return a Promise and the platform waits.

```js
edu.handlers.register("getState", () => ({
  domain:      { answers, currentStep, attempts, seed },
  interaction: { draftAnswer },
  ui:          { resultsOpen }
}));

edu.handlers.register("setState", (s) => {
  const d = (s && s.domain) || {}, i = (s && s.interaction) || {}, u = (s && s.ui) || {};
  answers = d.answers || {}; currentStep = d.currentStep || 0; attempts = d.attempts || {}; seed = d.seed;
  draftAnswer = i.draftAnswer || "";
  resultsOpen = !!u.resultsOpen;      // absent -> closed
  render();                            // rebuild the whole screen from these values
});

// when your state changes:
edu.state.sync();                      // optional — lets anyone watching live see it now
```

**The test that must hold:** build a state snapshot, pass it straight into your restore function, and **nothing on screen changes**. Read every written key, guarded by `|| {}`. Stronger version: build, reload the activity, and restore—it must look **identical**. This is about the **view**, not just data. Keep snapshots complete and deterministic: store inputs and seeds, not mid-flight timers, animation frames, or uncaptured network.

The platform captures the snapshot for resume, review **and** submission in both modes (on `complete()` in `self` mode, on the Turn-in button in `host` mode). It only rebuilds the activity. Grading is separate: `edu.signal.complete({score, maxScore, items})` in `self` mode, or optional `getResult` responder in `host` mode. **Never put learner's work in the grading payload, or grading in the snapshot.**

## View modes (recommended)

Normal, interactive mode is always default. To offer read-only views for teacher review: declare `embeducate:viewmodes` and implement `edu.handlers.register("viewMode", fn)` — the platform calls it when a teacher reviews a student's submission (after restoring that student's snapshot via the `setState` responder):

- `'read-only'` — render normally but freeze interaction (show exactly what the student left).
- `'summary'` — render your own read-only recap of the whole submission, regardless of the student's final screen.

## Minimal example

```html
<script src="https://embeducate.com/sdk/v1.js"></script>
<script>
  // …your activity…
  edu.signal.complete({
    score: 3, maxScore: 3,
    items: [{ id: "q1", skill: "equivalent-fractions", value: "2/4",
              display: "2/4", correct: true, score: 1, attempts: 1 }]
  });
</script>
```

## Full SDK surface

All calls are safe anytime—including `edu.handlers.register(...)`, covered in "State" above. They queue before the SDK loads and no-op outside Embeducate, so they never break a preview. You never need an `if (window.edu)` guard or to wait for a load event:

- `edu.signal.active()`/`edu.signal.idle()` bracket real interaction for time-on-task.
- `edu.signal.progress(fraction)` — 0–1 completion, at a **meaningful checkpoint**: a question answered,
  a step finished. **Each call permanently records a timeline node** the teacher can replay, so call it
  only at points worth replaying. An activity with no natural steps (a sandbox, a one-screen explorer)
  should **not call it at all**; its state is still captured on submission, and `edu.state.sync()` already
  keeps a live watcher up to date.
  **Call it *before* you advance your state to the next step — not merely before you re-render.** The node
  records whatever `getState` returns at that moment, so if you have already set `currentStep = next`, the
  node holds the *next* question even though the screen has not changed yet. (Measured: an activity that
  called `sync()` and `progress()` in the right order, but after `state.step++`, produced a timeline of
  blank unanswered questions.) Checkpointing after the switch records the *new* screen and the one the learner just left is
  never recorded at all — its answers can't be replayed, and a visual answer that lived there can never be
  pictured. Measured: a 3-step activity checkpointing after the switch recovered 1 of 2 visual answers;
  the same activity checkpointing before recovered 2 of 2.
  **Call `edu.state.sync()` before it**, or the node stores the state from your previous sync.
- `edu.signal.answered(itemId, { display, score, elementId })` — a question is finished. **Every
  question in `items[]` must get one**, the moment its answer is complete (leaving that step, pressing Next, finishing a
  drawing) — not on every keystroke. `itemId` matches the item's `id`; `display` and `score` are its values. For non-text answers, `elementId` names the holding element. The platform photographs it immediately—**so repaint before you signal**—the only time an earlier step's answer still exists. Signal again if the learner changes their answer.
- `edu.signal.complete({…})` — learner finished (shape above).
- `edu.signal.canSubmit(bool)` — host mode with `submit-policy=conditional`: current Turn-in button availability.
- `edu.attributes` — configuration set by activity deployer (see below).
- `edu.param(name)` reads a host-provided parameter.

## Make it configurable (optional, recommended)

Declare **attributes** so deployers can re-tune the activity without regenerating:

```html
<script type="application/ld+json">
{ "@type": "EmbeducateTask", "attributes": {
    "numQuestions":   { "type": "integer", "default": 10, "label": "Number of questions" },
    "maxDenominator": { "type": "integer", "default": 12, "label": "Largest denominator" }
} }
</script>
```

Read them at runtime: `const cfg = edu.attributes;`

## Constraints

- **Static site only** (HTML/CSS/JS/images/fonts) — no runtime server code.
- **No external data calls** — third-party `fetch`/XHR/WebSocket are blocked; the SDK is the only channel out. CDN libraries (React, Tailwind, …) are fine.
- **No PII** — don't ask the learner for a real name/email; the host handles identity.
- **You run in a content-sized iframe** — avoid `vh`/`svh`/`dvh` and `min-h-screen`
  for layout height; size with content, `px` or `rem` instead. (`vh` there means the frame's own height,
  which fights sizing.)

## Handoff

When the file is ready, tell the user to publish: drop it on embeducate.com for a live URL, embed code, and report link — no account needed.
