# Worksheets JSON format

Worksheets imports two document kinds: scored tests and interactive study canvases. Import a strict `.json` file, paste JSON, or import a ZIP containing `index.json` at its root and any referenced images. JSON cannot contain comments, trailing commas, `NaN`, or `Infinity`.

Files and pasted JSON are limited to 50 MB. The source document without its top-level `imageCache` is limited to 5 MB, and decoded images to 30 MB combined. A ZIP's root `index.json` also has a 5 MB uncompressed limit. Exported material JSON can include its images in `imageCache` and can be imported again within these limits.

The original annotated reference in the app is an unchanged archive. This document describes the current format. The additional prompts in [LLM-PROMPTS.md](LLM-PROMPTS.md) generate this format.

## Shared document metadata

| Field | Meaning |
| --- | --- |
| `kind` | `"test"` or `"visualization"`. Older tests may omit it. |
| `title` | Document title. Use nonempty text; tests without a title receive a default. |
| `description` | Optional plain-text description. |
| `tags` | Optional array of unique, nonblank source-topic strings. At most 64 tags, each at most 80 characters. Imported source tags are read-only in the library. |
| `folder` | Optional initial category path, such as `"History/Industrial Revolution"`, up to 200 characters. |

Source tags belong to the imported document and cannot be changed locally, including in the test JSON editor. User-added local tags and the library's folder placement are stored separately. Do not put `localTags`, `sourceTags`, `folderId`, or database IDs in a source JSON document. To use different source tags, import a new source document.

Use plain text in document content. Image references may be HTTPS/HTTP URLs, image data URLs, or paths to images included in a ZIP. For portable offline files, include images in the ZIP and reference paths such as `images/map.png`. Image-only multiple-choice options use their image path as the answer value.

## Tests

The `questions` array must contain at least one question. Every question needs a supported `type` and nonblank `prompt`. Supply a unique `id` using letters, numbers, `_`, or `-`; missing IDs are generated. Reserved IDs `__proto__`, `constructor`, and `prototype` are rejected.

| Test field | Meaning |
| --- | --- |
| `time_seconds` | Optional finite nonnegative number. Missing, `null`, or `0` means untimed. Positive durations must fit a safe timer deadline. |
| `allow_time_override` | Optional boolean. `false` locks the imported duration. |
| `randomize_questions` | Optional boolean; shuffle question order when starting. |
| `randomize_choices` | Optional boolean; shuffle supported options while preserving stored answer values. |

| Common question field | Meaning |
| --- | --- |
| `points` | Optional finite nonnegative number; defaults to `1`. Zero and fractional point values are supported. |
| `image` | Optional image reference. |

| Type | Required answer fields and behavior |
| --- | --- |
| `mcq_single` | `choices`: at least two distinct nonblank strings, or `{ "text": "…", "image": "…" }` objects. An object needs text or an image. `correct` exactly matches one choice's text, or its image path when text is absent. |
| `mcq_multi` | Same choices format. `correct` is a nonempty array of distinct choice values. Credit equals points × max(0, (correct selections − wrong selections) / number of correct choices). |
| `true_false` | `correct` is the boolean `true` or `false`, not a string. |
| `short_answer` | `correct` is one nonblank string or a nonempty array of accepted strings. Comparison ignores case and surrounding whitespace. `correct` may be omitted when `answer_regex` is supplied. |
| `numeric` | `correct` is a finite number. Optional `tolerance` is a finite nonnegative absolute tolerance, default `0`. Blank answers do not count as zero. |
| `matching` | `match_pairs` is a nonempty array of two-string `[left, right]` pairs. No `correct` field is needed. Credit is proportional to correctly matched pairs. |
| `ordering` | `items` contains at least two nonblank strings. `correct` is an array containing the same strings in the intended order, including repeated values when present. Credit is proportional to correct positions. |
| `fill_blank` | Put `__` in `prompt` for each blank. Longer runs of underscores also count as one blank. `correct` contains exactly one nonblank string per blank. Credit is proportional to correct blanks, ignoring case and surrounding whitespace. |
| `essay` | No `correct` field is needed. Essays remain pending for manual grading unless an optional regex check succeeds. |

An automatically graded answer is marked correct only when the complete answer is correct. Fractional partial credit does not round an incomplete answer into full credit. A test with pending manual checks has a provisional score.

### Regex checks for textareas

`short_answer` and `essay` support these optional fields:

| Field | Meaning |
| --- | --- |
| `answer_regex` | One nonblank regex source string or an array of 1–10 nonblank source strings. Maximum 1000 characters per pattern. |
| `regex_flags` | Flags string, default `"i"`. Each of `i`, `m`, `s`, and `u` may appear at most once. `""` requests no flags. Stateful `g` and `y` are not supported. |
| `regex_mode` | `"any"` by default, or `"all"` to require every pattern. |

Use regex source strings without `/…/` delimiters. JSON needs escaped backslashes: `"\\bcat\\b"` represents a word-boundary pattern, and `"^\\s*cat\\s*$"` allows surrounding whitespace. Patterns test the answer string; use `^` and `$` when the whole response must match. Blank responses never pass a regex check.

Short answers pass when a literal accepted answer **or** the regex check succeeds. A regex miss is incorrect for a short answer. A successful essay regex check awards full points and records `"Regex matched; manual review available."`; a miss leaves the essay pending. Keyword matching does not establish semantic correctness, so review remains available.

Authored regexes execute in an isolated Worker, with a 750 ms deadline for the batch. Completed checks are retained. Worker startup failures, unavailable Workers, and unfinished checks become pending manual checks. Validation compiles syntax without running authored patterns on the main thread. Avoid broad accept-any expressions, nested quantifiers, and expensive backtracking.

Complete example: [examples/regex-test.json](examples/regex-test.json).

```json
{
  "kind": "test",
  "title": "Capital cities",
  "tags": ["Geography"],
  "folder": "Study/Geography",
  "time_seconds": 300,
  "allow_time_override": true,
  "randomize_questions": false,
  "randomize_choices": true,
  "questions": [
    {
      "id": "q1",
      "type": "short_answer",
      "prompt": "What is the capital of France?",
      "correct": ["Paris", "the city of Paris"],
      "answer_regex": "^\\s*(?:the\\s+)?(?:city\\s+of\\s+)?paris[.!]?\\s*$",
      "regex_flags": "i",
      "regex_mode": "any",
      "points": 1
    }
  ]
}
```

## Interactive visualizations

A visualization is a separate study document, not a timed test. Its root contains `kind: "visualization"`, a title, an `elements` array containing 1–500 elements, and optional shared metadata.

`canvas` optionally supplies `{ "width": 1600, "height": 1000 }`. Each dimension is between 100 and 12000. Native elements can be moved and viewed around the canvas; layouts without coordinates receive automatic placement.

Each element has `type`, an optional unique safe `id` of up to 120 characters, and optional `x`, `y`, `width`, `height`, `title`, `text`, and `color`. Coordinates are nonnegative and at most 10000; dimensions are between 100 and 10000. Each block's right and bottom edges must stay at or below 11960, leaving room inside the 12000-pixel canvas limit. Colors can be `indigo`, `green`, `amber`, `rose`, `violet`, `slate`, or a 3-, 4-, 6-, or 8-digit CSS hex color. Supply explicit positions and dimensions when the relationship between elements matters.

| Element type | Content fields |
| --- | --- |
| `text` | Required `text`; optional `title`. |
| `event` | Required `date` and `text`; optional `title`. Dates are human-readable labels. |
| `timeline` | `items`: 1–200 objects `{ "date": "…", "title": "…", "text": "…", "image": "…" }`. Date and title are required; text and image are optional. |
| `image` | Required `image`; optional `alt`, `caption`, and up to 100 `hotspots`. Each hotspot has percentage `x` and `y` between 0 and 100, plus `label` and `text`. |
| `card` | Required `front` and `back` text for a flip card. |
| `reveal` | Required `title` and `text`; optional `summary` for expandable study content. |
| `comparison` | `columns`: 2–8 `{ "title": "…", "items": ["…"] }` objects. Each column has 1–500 items. |
| `list` | `items`: 1–500 nonblank text strings. |
| `table` | `headers`: 1–30 nonblank text strings; `rows`: 1–500 arrays of text strings with the same column count. |
| `quiz` | Required `prompt`, 2–30 unique text `choices`, and `correct` matching one choice. Optional `explanation`. This is a local self-check rather than a scored test attempt. |
| `group` | Required `title`; a labelled region on the canvas. |
| `embed` | At least one nonblank `html`, `css`, or `js` string for a custom interactive block. |

Optional root `connections` contains up to 1000 directed relationships:

```json
{
  "from": "cause",
  "to": "effect",
  "label": "contributed to",
  "color": "indigo"
}
```

`from` and `to` must reference existing element IDs. A connection expresses what the source supports: chronology alone does not prove causation. Mark interpretations and uncertain dates in visible text.

### Custom embeds

Prefer native elements when they fit. Use separate `html`, `css`, and `js` fields for an interaction that needs custom code. HTML is limited to 500000 characters; CSS and JavaScript to 250000 characters each. Scripts and inline event handlers inside the HTML field are removed; executable code belongs in `js`. The user must explicitly run an embed. It runs inside a sandboxed iframe, without access to parent storage, and network access is blocked. No external packages or remote scripts are available. Embed scripts are not CPU-limited, so keep them small and use trusted code.

Complete study canvas: [examples/history-canvas.json](examples/history-canvas.json). Copy either visualization prompt from [LLM-PROMPTS.md](LLM-PROMPTS.md) to generate source-grounded canvases from uploaded material.
