Skip to content

feat(h5p-author): a check of saved content, which the build runs last (#107) - #113

Merged
alekswebnet merged 1 commit into
feat/embed-plan-importsfrom
feat/author-check-content
Nov 2, 2026
Merged

alekswebnet merged 1 commit into
feat/embed-plan-importsfrom
feat/author-check-content

Conversation

@alekswebnet

Copy link
Copy Markdown
Contributor

Closes #107.

Stacked on #112 (#109): the base is feat/embed-plan-imports, because packageLibraries takes a package's own libraries by the player's ownLibraries, which #112 adds to ./plan. Once #112 is merged I retarget this to main.

What it adds

  • checkContent(libraries, { type, params }) checks content in the form H5P saves it, as the build checks what it writes, and returns { params, problems, notes }. The libraries are loaded by loadLibraries, as for a build. type is the content type with the version it was saved with, as h5p.json names it (H5P.QuestionSet 1.20).
    • Files and sub-content metadata are taken as saved, and held to what the platforms keep of them: H5PContentValidator's keys per file type, and the licences the core's copyright dialog looks up without a check.
    • Each part keeps its subContentId. A part without one, with one H5P would not keep, or with an id an earlier part already has gets a new id, with a note. Copy and paste in the editor leaves such duplicates.
    • A markup field's text is not wrapped again.
    • Then the layouts run (after), each part without a title takes its content type's, and a hidden field nothing set is a problem.
  • packageLibraries(files, pack) is a library source over a package's own library folders, with the pack behind it for what the package does not carry. files is { names, read }, so any zip reader serves. A folder counts as the package's own by the player's rule (ownLibraries).
  • The build runs the check last on what it is about to write, so the build and an editing tool hold content to one set of rules. The layouts' after hooks, the empty titles and the unset fields moved out of build.mjs into the check. The build adds what the check finds, except where the source has that problem already, or where the generator refused what was written there and left a value missing. Every problem still comes at once.
  • Game Map's before is split as the issue proposed. It keeps the conversion of a source's numbers and labels into the stored text. The checks run on the stored form in after: positions from 0 to 100, neighbours as indices of other stages, joined both ways.
  • A group that is sub-content now gets an id in a build too, as the editor's initSubContent gives one. Summary's statements and Single Choice Set's choices had none from a build, and Summary names each statement's statements by that id.

The rule for which semantics (decided in #107)

The package's own libraries where it carries a major of one; the pack's where it does not. Where the content names another minor than the one it is checked against, as an export without its libraries does against the pack, a field only that minor has is noted and kept, not refused. The player upgrades older content as it plays it, which can move such a field: Question Set 1.21's upgrade step moves 1.20's backgroundImage into override. That upgrade runs in the GPL runtime's frame, so h5p-author cannot run it. Content newer than the pack is played without the field.

What saved content holds

Run over the 88 real packages in the local test corpus (not in git): 61 report nothing. Three things the editor saves for an author who never touched them are taken as such, all found on that corpus:

  • an optional group holding nothing an author has to write, empty texts in it included (Interactive Video's adaptivity);
  • the item the editor starts an optional list with (Interactive Video's subtitle track);
  • an optional select left at its empty choice (goto.type: "").

The other 27 hold what their editor let an author save and the build does not:

  • required alt texts and scenario titles left empty;
  • keys an older editor wrote (Drag and Drop's dropZones[].type, Branching Scenario's contentId);
  • styles pasted from Word;
  • booleans saved as strings.

These are reported as problems. A tool that changes one part should keep only the problems under the paths it changed; the README and AGENTS.md say so.

Content written without its defaults comes back with them, as the editor saves it. The demo's hand-written packages are like that, so they come back changed but report nothing.

Checked

  • Acceptance, every type file's example: built, then checked as saved content against its own libraries and against the pack. Nothing reported, nothing changed, in all 32. This is now part of apps/skills' every-example test, which passes (73 tests).
  • Acceptance, the demo's packages: checked against their own libraries, nothing reported. formulas.h5p, an export without its libraries, is checked against the pack, also with nothing reported. markup.h5p reports exactly the markup it carries to be removed. All in tests/check.test.ts.
  • Author tests: 129, 20 of them new. 8 cover the generator's saved mode; 12 cover checkContent, packageLibraries and the Game Map split against the real pack. I broke each saved-mode rule in turn, and each broken rule fails at least one test.
  • Content-type sweep: 110 of 110 plays pass locally. The generator now gives a group that is sub-content an id in its lenient mode too, and the sweep shows the same results as main for Summary and Single Choice Set (no finished in either). CI runs it again, since it starts on any change to generator.mjs.
  • Typechecks: the workspace typecheck passes, including the emitted declarations through tests/package-types.ts.

Not in this PR

  • h5p-author: an entry that runs in a browser #108, the browser entry. check.mjs and metadata.mjs have no Node import of their own, like libraries.mjs; generator.mjs and types.mjs still take randomUUID from node:crypto.
  • Saved hidden positions written as null (one corpus package) are reported, though a layout could place them.

🤖 Generated with Claude Code

…#107)

checkContent(libraries, { type, params }) checks content in the form H5P
saves it, as the build checks what it writes: files and sub-content
metadata as saved, held to what the platforms keep of them; each part's
subContentId kept, a new one only for a part without one, with one H5P
would not keep or with an earlier part's; a markup field's text not
wrapped again. The layouts' after hooks, the empty titles and the unset
fields moved into it, and the build runs it last on what it writes.
Game Map's before keeps only the conversion of a source's numbers and
labels; its checks run on the stored form.

packageLibraries(files, pack) serves a package's own libraries where it
carries a major, the pack's otherwise, by the player's ownLibraries. Where
the content names another minor than the one checked against, a field
only that minor has is noted and kept.

A group that is sub-content now gets an id in a build, as the editor's
initSubContent gives one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alekswebnet
alekswebnet added this pull request to stack #114 November 2, 2026 20:20
@alekswebnet
alekswebnet merged commit d9d8c82 into main Nov 2, 2026
8 checks passed
@alekswebnet
alekswebnet deleted the feat/author-check-content branch November 3, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

h5p-author: a check of saved content, which the build also runs on what it writes

1 participant