Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion design/author.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ already holds text to the content check's rules, from the pack an untrusted chec
`apps/skills` plays every example untrusted too. Measured on a five-question quiz, at four
characters a token: `content.json` was about 3,470 tokens to write by hand, after reading 3,900 to
9,500 of a type's semantics; the source is 1,000 to 1,800 tokens, written from a type file of 200
to 3,000. Eighteen things the code does not say by itself:
to 3,000. Nineteen things the code does not say by itself:

- **One generator, three ways to run it.** The third, `saved`, checks content as H5P stores it
(*Saved content is checked as the build checks what it writes*, below). `ContentGenerator` was the sweep's
Expand Down Expand Up @@ -396,6 +396,47 @@ to 3,000. Eighteen things the code does not say by itself:
Interactive Video, Image Hotspots, Image Choice, Game Map): of 12 first replies 2 were right at
once with the sentence and 2 without, the Accordions. The skill gives the type files' address
on GitHub too, for a chat that has its text and can open a link.
- **A source's own fields do not hide its content, and its outer shape is taken as it is meant**
(2026-11-10, #174). Gemini in Chrome, given a link to the skill's folder, which shows a chat the
file names and none of the text, wrote a book its own way: `contentType` for `type`, and the
title, licence and authors in a `metadata` object, as H5P keeps them. The build checked a
source's own fields, then the type's name, then the content, and stopped at each: seven
problems of the outer shape, then thirty of the content, and a package at the third try.
Measured on Haiku in that position, the request as it was worded and no skill text, six content
types, two runs each, the build page's copy pasted back: with 0.7.0 none was right at once, 3
by the second round, 8 of 12 by the fourth, and four never. All twelve first sources had the
content's fields beside `type` with no `params`, nine named the type as it is spoken of, and six
of the nine second rounds were one problem, `the library pack has no content type
interactive-book`, with no name offered: the next guesses were `book` and `flashcards`.

So a build reads a source with the pack's names (`sourceOf`), and takes what can only be meant:
the content type under `contentType` or `library`, or named loosely as `type`, the same letters
and digits in any case with or without `H5P.` (`names.mjs`); the source's own fields in
`metadata`, and in no other object, since Question Set's `introPage` has a title too; the
content's fields beside `type`. Each is a problem, said once with what to write, and the content
is generated and checked in the same build wherever the type is known, with a stand-in for what
is missing. A name that is no type has the nearest offered: those its letters are part of, and
those they end with, not one they begin with, since the "image" of "image choice" is no lead.
A title is not tried, "Multiple Choice" or "Image Choice": the pack's index has none, and
reading them means fetching every part, 147 of them and some 15 MB, to name one type. A number
with no point is no version: "Question Set 2" is no Question Set. Sub-content that names its
library under `type` or `contentType` is taken the same way, its parameters in `params` or
beside the name, where one of the two names a library the field takes, at the major written
where the field takes it, and the loader loads that library, so what is written for it is
checked against its fields and not against none. No other key: in the 328 sources the tests
stored, every name found under another one was a text that reads like one, a card's answer
"table" six times, `main: "Question"` three times, `title` and `libraryTitle` "Text". A title beside the
parameters that is no field of the library is the sub-content's own, its `metadata.title`, in a
library field's place or in its group's, which had been two problems a chapter.

With it 10 of the 12 were right by the second round and all 12 by the third, and every one
plays. Of the 263 sources the earlier tests stored, written with the skill's text, 259 have the
same problems, the other four being books whose chapters had a title beside them, 13 problems
now 7, and none builds with one build and not the other; the 36 sources of the type
files and the quiz format build the same package. `readSource` keeps its contract, a source's
own problems thrown, without the pack's names. What the build page must do with it is its own:
hand the type's file back for a type that is named loosely, which its rule, the source's `type`
as written, does not.

Not built: editing an existing package, which a tool beside it does with `checkContent` as its
check; layout for Drag and Drop and Crossword; and any judgement of whether content is right, which
Expand Down
14 changes: 14 additions & 0 deletions packages/author/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ h5p-author: The source has 3 problems:
params.slides[0].elements[0].params.text: has <h1>, which this field does not take; it takes <a> <br> …
```

A source's own fields are read the same way, for a chat that wrote one with no skill beside it.
What its outer shape plainly means is taken and said once, and the content is checked in the same
round: the content type under `contentType` or `library`, or named as it is spoken of
(`"interactive-book"`, `"Question Set"`), the title, language, licence and authors in `metadata`,
the content's fields beside `type`.

```
h5p-author: The source has 4 problems:
contentType: is not a field of a source: the content type is its type: write "type": "H5P.InteractiveBook"
metadata: is not a field of a source: title, license, authors are written beside type, not in an object: write "title": …, "license": …, "authors": … at the top
language: expected a BCP 47 language tag, such as "en" or "de-AT"
params.chapters[0]: is the params of sub-content, without the sub-content around them: write params.chapters[0] as { "library": "H5P.Column", "metadata": { "title": … }, "params": { "content": … } }
```

The quiz format the h5p-author skill published first (`{ "kind": "question-set", "questions": [ … ] }`)
is still taken, for a Multiple Choice question or a Question Set of them.

Expand Down
9 changes: 6 additions & 3 deletions packages/author/lib/browser.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js'
import { crc32 } from '@missing-elements/h5p-normalize/crc32'
import { packLibrariesIn } from '@missing-elements/h5p-offline-player/plan'
import { composePackage, readSource, writePackage } from './build.mjs'
import { composePackage, sourceOf, typeNames, writePackage } from './build.mjs'
import { MediaStore } from './media.mjs'
import { Pack } from './pack.mjs'
import { SourceError } from './source-error.mjs'

export * from './core.mjs'

Expand Down Expand Up @@ -154,11 +155,13 @@ export async function buildPackage(input, options) {
secure()
const { libraries, files = {}, fetch: fetchImpl = pageFetch } = options ?? {}
if (!libraries) throw new TypeError('Name the library pack: a pack openPack opened, the pack export of @missing-elements/h5p-libraries, or the address of a copy of its parts/ folder')
const source = readSource(input)
const pack = libraries instanceof Pack ? libraries : await openPack(libraries)
try {
// With the pack's names, as the command's build reads a source (#174).
const read = sourceOf(input, typeNames(pack))
if (!read.typed) throw new SourceError(read.problems)
const media = new MediaStore(pageMedia(files), { fetch: fetchImpl })
const built = await composePackage(source, pack, media)
const built = await composePackage(read.source, pack, media, read.problems)
const writer = new ZipWriter(new BlobWriter('application/zip'), { useWebWorkers: false })
await writePackage(writer, built, pack, async () => {
for (const item of media.files) {
Expand Down
Loading
Loading