@openpresentation/opf 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ import { SchemaName } from './schemas.js';
4
4
  /**
5
5
  * RR-78: the version of the catalog record schemas (spec/schemas/<kind>.schema.json) this core reads. A catalog package
6
6
  * declares the version its records target (`opf.catalogSchema` in its package.json, `CATALOG_SCHEMA` in its exports, as
7
- * @openpresentation/gallery does); the Node defaults refuse a gallery that targets another one. A record schema change that
8
- * old records no longer validate against raises it.
7
+ * @openpresentation/gallery does). Core and the gallery release in lockstep, and `pnpm check:catalog-schema` holds the two
8
+ * constants equal. A record schema change that old records no longer validate against raises it.
9
9
  */
10
10
  declare const CATALOG_SCHEMA = 4;
11
11
  /** Kinds of catalog display metadata: published by catalogs for pickers, never referenced by documents. */
@@ -0,0 +1,9 @@
1
+ import { gallery } from '@openpresentation/gallery';
2
+
3
+ // src/node/catalogs.ts
4
+ var DEFAULTS = Object.freeze([gallery]);
5
+ function defaultCatalogs() {
6
+ return DEFAULTS;
7
+ }
8
+
9
+ export { defaultCatalogs };
@@ -1,4 +1,4 @@
1
- import { defaultCatalogs } from './chunk-Z7XNHIW7.js';
1
+ import { defaultCatalogs } from './chunk-AQEAZDL2.js';
2
2
  import { localAssetSources, OPFApiError, parseSlideSelection, OPFExportError, asApiError, OPFImportError } from './chunk-I6G32TB5.js';
3
3
  import { paginate } from './chunk-7UO6H32I.js';
4
4
  import { deckFormatOf, readDeckReport, stringify } from './chunk-SYCHWS7C.js';
@@ -117,7 +117,7 @@ function localAssetProblems(deck, root) {
117
117
  }
118
118
  var RENDER_PACKAGE = "@openpresentation/opf-render";
119
119
  var PPTX_PACKAGE = "@openpresentation/opf-pptx";
120
- var PEER_RANGES = { [RENDER_PACKAGE]: "^0.30.0", [PPTX_PACKAGE]: "^0.30.0" };
120
+ var PEER_RANGES = { [RENDER_PACKAGE]: "^0.31.0", [PPTX_PACKAGE]: "^0.31.0" };
121
121
  var PPTX_TEXT_OPTIONS_FROM = "0.22.0";
122
122
  var numbers = (version) => version.replace(/[-+].*$/, "").split(".").map((part) => Number.parseInt(part, 10) || 0);
123
123
  function atLeast(version, minimum) {
@@ -1,5 +1,5 @@
1
- export { convertFiles, deckFileFormat, exportFormatOf, openDeck, planConversion, readInput, saveDeck, writePlanned } from './chunk-AFYXG3IB.js';
2
- import './chunk-Z7XNHIW7.js';
1
+ export { convertFiles, deckFileFormat, exportFormatOf, openDeck, planConversion, readInput, saveDeck, writePlanned } from './chunk-XSCZZWYI.js';
2
+ import './chunk-AQEAZDL2.js';
3
3
  import './chunk-I6G32TB5.js';
4
4
  import './chunk-UJSAR6GF.js';
5
5
  import './chunk-PJSPHHRK.js';
@@ -43,8 +43,8 @@ declare const RENDER_PACKAGE = "@openpresentation/opf-render";
43
43
  declare const PPTX_PACKAGE = "@openpresentation/opf-pptx";
44
44
  /** Peer ranges. The CLI checks the features it calls rather than the version, so a newer release in range keeps working. */
45
45
  declare const PEER_RANGES: {
46
- readonly "@openpresentation/opf-render": "^0.30.0";
47
- readonly "@openpresentation/opf-pptx": "^0.30.0";
46
+ readonly "@openpresentation/opf-render": "^0.31.0";
47
+ readonly "@openpresentation/opf-pptx": "^0.31.0";
48
48
  };
49
49
  interface Diagnostic {
50
50
  code: string;
package/dist/docs.js CHANGED
@@ -66,7 +66,7 @@ var docsData = Object.freeze([
66
66
  "slug": "default-catalog",
67
67
  "file": "docs/default-catalog.md",
68
68
  "title": "Catalogs and the gallery package",
69
- "markdown": '# Catalogs and the gallery package\n\nOPF 0.15 keeps catalogs out of the engine. A document embeds every record it uses, hosts register the catalogs they\ntrust, and core ships no records. The **default catalog** is the npm package **`@openpresentation/gallery`**, authored in\n`packages/gallery` of this repository (RR-78, moved in by MR-23) and released with the other packages under one shared version;\npptx.gallery presents it and consumes the published package. Core and the CLI\ndepend on it and register it in Node; core has no `/catalog` subpath (removed in 0.19, with no alias).\n\nThis page is the contract: the document shape, the one resolution rule, how hosts register catalogs, the authoring\nhelpers, the diagnostics, and who authors and releases the gallery package. The design behind it is\n[0.15-design.md](programs/format-audit/0.15-design.md).\n\n## Catalogs in a document\n\n`catalogs` groups the records a document embeds by the catalog they came from. Inside a group, records are keyed by\nkind and then by id:\n\n```json\n"catalogs": {\n "default": {\n "source": "https://www.pptx.gallery",\n "layouts": { "two-column": { "name": "Two column", "regions": [{ "name": "title" }, { "direction": "row", "regions": [{ "name": "first", "accepts": ["text"], "flow": "auto" }, { "name": "second", "accepts": ["text"], "flow": "auto" }] }] } },\n "themes": { "minimal": { "name": "Minimal", "colorScheme": "cool-horizon", "fontScheme": "aptos" } }\n },\n "acme": { "source": "pkg:@acme/opf-catalog", "layouts": { "hero": { "name": "Hero", "regions": [{ "name": "title" }, { "name": "media", "accepts": ["image"], "flow": "none" }] } } },\n "custom": { "layouts": { "q4-special": { "name": "Q4 special", "regions": [{ "name": "title" }, { "name": "chart", "accepts": ["chart"], "flow": "none" }] } } }\n}\n```\n\n- **`default`** is the catalog bare ids come from. It needs a `source` when it holds records. Omitted: bare ids fall\n back to the host\'s default catalog. `"default": false`: no catalog fallback at all, so every bare id must be embedded.\n- **`custom`** holds the records the document defines itself. It has no source. Forking a catalog record copies it here\n under a new id, so the change of ownership is visible.\n- **Any other name** (lowercase kebab-case) is an extra catalog, identified by its `source`: an HTTPS URL or a `pkg:`\n package reference. The name is the prefix its references use (`acme:hero`).\n- The kinds are `layouts`, `themes`, `colorSchemes`, `fontSchemes`, `narratives`, `audiences`, `purposes` and `tones`.\n- An embedded record has the fields of its kind\'s companion schema without `$schema` and `id` (the key is the id), and\n without the catalog\'s `x-*` display metadata. `validate` checks each one against the companion schema\n (`opf/catalog-record`).\n\n## References\n\nEvery content reference is a bare `id` or `name:id`, and every prefix must name a group (`opf/undeclared-catalog`\notherwise, a format error that engines reject at their format check). URLs and `pkg:` strings are not references; a named group replaces them. The references are\n`Slide.layout`, `design.theme`, `design.colorScheme` (or its `id`), `design.fontScheme` (or its `id`), the same three on\n`Slide.design`, `narrative`, `audience`, `purpose` and `tone` (a string or an object\'s `id`), a theme record\'s\n`colorScheme` and `fontScheme`, and a `language` object\'s `fontScheme` and `googleFontScheme`. An `audience` or\n`purpose` string that is not an id (it has spaces, capitals or punctuation) is free text, never looked up.\n\n## Resolution\n\nOne rule for every kind; nothing is ever fetched:\n\n1. A bare id: `catalogs.custom`, then the records embedded under `catalogs.default`, then the catalog the host\n registered for `default.source` (the host\'s default catalog when `default` is omitted; nothing when it is `false`).\n2. `name:id`: the records embedded under `catalogs.<name>`, then the catalog the host registered for its `source`.\n3. A reference written inside an embedded record (a theme\'s `colorScheme`) resolves in that record\'s own group first,\n so acme\'s `brand` theme finds acme\'s `ocean` before the default catalog\'s.\n4. Otherwise the reference is **unresolved**: the `opf/unresolved-reference` warning names the reference and the\n source it was looked for in, a slide composes automatically, and a design uses the engine default. A strict export\n fails with the same reference.\n\nA record embedded under `catalogs.default` or a named group whose registered catalog has no record of that kind and id is the warning `opf/catalog-record-not-in-source`: it is the document\'s own record and belongs in `catalogs.custom`. The check is silent when no catalog is registered for the group\'s source, for a record the catalog has with other content (an update difference) and for `custom`; rendering is unchanged.\n\nA slide with no `layout` is automatic composition, not a missing reference, and has no finding. Engines never\nsubstitute a different layout for one that does not resolve.\n\n## Registering catalogs\n\nEvery entry point that resolves references takes the same option, `catalogs`: the catalogs the host registered,\nmatched by `source`.\n\n> **The host-default rule.** The first registered catalog is the default for documents that omit `catalogs.default`:\n> their bare ids resolve in `custom`, then in that catalog. A document whose `default` names a `source` uses the\n> registered catalog with that source, and nothing when the host did not register it, even if it registered others.\n> `"default": false` uses no catalog for bare ids. Without the `catalogs` option the browser build and every engine\n> register nothing, so only what the document embeds resolves; in Node, core\'s file API and `validate`, `stats`,\n> `paginate`, `embed` and `edit` register `gallery`, as the CLI does. Hosts (the CLI, the editor app, the sites)\n> register `@openpresentation/gallery`; libraries and engines pass the option through.\n\n```js\nimport { validate, resolveSlideContext, paginate, embed } from \'@openpresentation/opf\';\nimport { gallery } from \'@openpresentation/gallery\';\n\nconst catalogs = [gallery, acmeCatalog]; // acmeCatalog = { source: \'pkg:@acme/opf-catalog\', layouts: { hero: { \u2026 } } }\nvalidate(document, { catalogs });\nresolveSlideContext(document, 0, { catalogs, strictReferences: true }); // throws OPFUnresolvedReferenceError\npaginate(document, { catalogs });\n```\n\nA registered catalog has the shape of a document group with `source` required: `{ source, layouts?: { <id>: record },\nthemes?, \u2026 }`. Records may keep their `x-*` display metadata there; embedding strips it. `resolveSlideContext`,\n`paginate`, `validate`, `stats`, `resolveScriptFonts` (`/composition`), `resolveReference`, `catalogRecords`, `embed`,\n`copySlides`, `moveToCustom` and `updateFromCatalog` all take it, and opf-render, opf-pptx and opf-editor pass it through to core, so a\nrecord a host registers resolves the same in preview, editor and export. Without it only what the document embeds\nresolves. A `catalogs` value that is not an array of registered catalogs throws `OPFCatalogsOptionError`\n(`code: "invalid-catalogs"`) at every entry point. `resolveSlideContext(...).resolved.provenance` (and\n`resolveDesignRecords(...).provenance`) says where each resolved layout, theme, colour scheme and font scheme came from\n(`{ kind, reference, id, group, source?, origin }`, origin `document` or `host`), so engines record provenance without\nresolving again.\n\n`@openpresentation/gallery` exports:\n\n| Export | What it is |\n| --- | --- |\n| `gallery` | The content records, keyed by kind and id, registered under `GALLERY_SOURCE` |\n| `GALLERY_SOURCE` | `https://www.pptx.gallery` |\n| `CATALOG_SCHEMA` | The version of core\'s catalog record schemas the records target (also `opf.catalogSchema` in its `package.json`) |\n| `catalogDisplay` | Display metadata for pickers: `chartTypes`, `languages` and `socialPlatforms` by id |\n| `catalogIndexes` | Each kind\'s `index.json` |\n| `catalogManifest` | A content hash per kind, and the pptx.gallery commit of the one-time 1.x snapshot (never re-synced: records have been authored in the gallery package since 2.0.0) |\n\n`@openpresentation/gallery/previews` exports the static HTML layout previews: `layoutPreviews`, `layoutPreviewIndex`,\n`layoutPreviewSlugs`, `getLayoutPreview` and `hasLayoutPreview`.\n\nThe region-list source contract (FA-33) reads catalog record generation `CATALOG_SCHEMA = 4` (exported from core).\nThis package compatibility generation is separate from the layout schema URI: layout records retain\n`https://openpresentation.org/schema/opf-layout/v2` and per-kind `schemaVersion: 2`. Generation 4 refuses\ngeneration-3 Gallery records because `areas`, `columns`, `rows` and the `regions` map no longer validate; like generation 3\n(nested area arrays) it does not introduce a `/v3` schema URI.\n\nCore reads catalog schema `CATALOG_SCHEMA` (exported from its root). When the installed gallery declares another one,\ncore never registers it: the Node defaults throw `OPFApiError` `gallery-schema-mismatch` and the CLI stops with the same\ncode, naming both versions. No browser entry of core imports catalog data; `pnpm check:catalog-free` holds that with an\nesbuild metafile budget.\n\nOPF 0.19 removed `@openpresentation/opf/catalog` (`defaultCatalog`, `DEFAULT_CATALOG_SOURCE`, `catalogDisplay`,\n`catalogIndexes` and the layout previews) and core\'s `spec/catalogs/` and `spec/previews/` files, with no alias: import\nthe same records from `@openpresentation/gallery`, where `defaultCatalog` is `gallery` and `DEFAULT_CATALOG_SOURCE` is\n`GALLERY_SOURCE`.\n\n## Authoring helpers\n\n- **`embed(document, { catalogs })`** embeds each referenced record once, in the group it resolves in, together with\n the records it references (a theme\'s colour and font schemes). A bare id from the host default goes under\n `catalogs.default` with the catalog\'s source. Records already embedded are kept as they are; `x-*`, `$schema` and `id`\n are stripped. It returns `{ document, added, unresolved }` and is idempotent. Authoring tools call it on save and\n export; `opf embed` does it on the command line.\n- **`copySlides(from, to, indexes, { catalogs, at })`** copies slides with their records. Groups match by `source`, not\n by name: references are rewritten to the target\'s name for that source, and a missing group is added (renamed\n `<name>-2` when its name is taken). A source document without `catalogs.default` inherits the host default\'s\n source; when the target\'s default has another source, or is `false`, that catalog is added to the target as a named\n group and the copied bare references get its prefix, so copied slides never change catalog. A record the target\n already has with the same content is reused. A `custom` id\n the target uses for different content is renamed `<id>-2`. A catalog record whose revision differs from the one the\n target resolves moves into `custom` as `<id>-2`, so the copied slides look the same, and is listed in `renamed`.\n `renamed` lists only the records a call creates under a new id: a later copy that reuses one is not a rename.\n- **`moveToCustom(document, { kind, reference }, { catalogs })`** moves a record embedded under `default` or a named\n group into `custom`: the fix `opf/catalog-record-not-in-source` suggests. Every reference that named it is rewritten\n to name it in `custom`, and its own references keep naming what they named. It keeps its id unless `custom` holds a\n different record under it (then `<id>-2` and up, reported in `renamed`; an identical one is reused). It returns\n `{ document, from, to, references, patch, renamed? }`, where `patch` is RFC 6902 for review and undo, and throws\n `OPFMoveToCustomError` (`invalid-reference`, `already-custom`, `not-embedded`, `invalid-id`). Fork:\n `moveToCustom(document, ref, { id })` copies the record into `custom` under the new id (`<id>-2` on conflict) and\n rewrites every reference to the copy, qualified references inside other records included, so the original is dropped\n from its group; an editor calls it on the first edit of a catalog record and applies the edit to the copy.\n- **`updateFromCatalog(document, catalogs, refs?)`** compares the records embedded under `default` and the named groups\n with the registered catalogs\' current ones and returns `{ changes, patch }`. Nothing changes until the author applies\n `patch` (`applyPatch`); `custom` records are never compared.\n- **`resolveReference`**, **`parseReference`** and **`catalogRecords`** (the records a picker can offer, with the\n reference to write for each) are the building blocks.\n\n## Engine vocabularies and engine defaults\n\nValues an engine must understand to draw are not catalog references:\n\n- `chart.type` is a schema enum;\n- the keys of `Organization.socials` and `Speaker.socials` are a schema enum, linked with URL patterns in code;\n- `language` is a BCP-47 tag (or a `Language` object with a `bcp47` tag); engines know the script, direction, OOXML\n culture tag and default script fonts of the vocabulary\'s tags and infer the script of any other well-formed tag.\n\nThey are listed in [`spec/reference/engine-vocabularies.json`](../spec/reference/engine-vocabularies.json) and exported\nas `CHART_TYPES`, `SOCIAL_PLATFORMS` and `LANGUAGES`. Their labels, descriptions and icons stay in the catalog as\ndisplay metadata (`catalogDisplay`).\n\nWhen nothing names a theme, colour scheme or font scheme, or a reference resolves nowhere, every engine draws with the\nsame engine defaults: [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json), exported as\n`ENGINE_DEFAULT_THEME`, `ENGINE_DEFAULT_COLOR_SCHEME` and `ENGINE_DEFAULT_FONT_SCHEME`. They are the drawing fields of\nthe gallery\'s `minimal`, `cool-horizon` and `aptos` records, compiled into code.\n\n## Ownership\n\n- **The gallery package owns every catalog record.** Since gallery 2.0.0 records, layouts and theme variants are authored\n by pull requests in `packages/gallery` (the product statement "pptx.gallery owns every default record" is retired, D11 of the\n monorepo program: the site presents the catalog, it does not author it); each id has one owner. Its\n display metadata lives under `x-*` members. pptx.gallery (the site, a separate repository) consumes the published package,\n adds its own presentation overlay and does not write records. A record that needs an engine change is one pull request,\n tested by one `verify` run.\n- **The gallery package owns the records and releases with the other packages.** `packages/gallery` is a tracked package of this repository (the monorepo program, D13): records, layouts and theme variants are authored by pull request here, its manifest holds a content hash per kind and the commit of the one-time 1.x snapshot from pptx.gallery, and it takes the repository\'s one shared version line (0.26.0 and later; gallery 1.0.0 to 5.0.0 stay on npm). The earlier `pnpm install` materializer of ignored fixtures is gone.\n- **A host chooses its catalog version.** Hosts register the catalog they choose; documents carry the records they were\n saved with. The packages are released together, so the pair a release ships (core and the gallery at the same version) is\n tested together.\n\n## The `@openpresentation/gallery` package\n\nRR-78: the same records are published as the npm package\n[`@openpresentation/gallery`](https://www.npmjs.com/package/@openpresentation/gallery) from `packages/gallery`, with the\nrepository\'s one shared version (from 0.26.0; the earlier independent line ended at 5.0.0). A record change is an ordinary\npull request (`catalog/<kind>/<id>.json` plus its index entry, then `pnpm rehash` and `pnpm previews` in the package, checked\nby `pnpm check:changed`); a `changes/<slug>.md` fragment records it and the release workflow publishes it with the rest:\n\n```js\nimport { gallery } from \'@openpresentation/gallery\';\nvalidate(document, { catalogs: [gallery] });\n```\n\n`gallery` replaces what `@openpresentation/opf/catalog` exported as `defaultCatalog` (`source` and the eight content kinds, records\nkeyed by id without `$schema`, `id` or `x-*`); the package also exports `GALLERY_SOURCE`, `CATALOG_SCHEMA`,\n`catalogDisplay`, `catalogIndexes` and `catalogManifest`, and `/previews`. New records go in any release; an id removed\nor renamed, a changed drawing or a record schema change is declared in the `changes/` fragment and makes the release a minor\none (a patch is refused), which the pixel-stability check (`pnpm check:stability`, against the `latest` release) enforces.\nFrom OPF 0.19 core and the CLI depend on it (`workspace:^` in this checkout, published as `^X.Y.Z` of the same release), core\'s\nNode defaults register `gallery`, and core drops `/catalog` and `spec/catalogs/`. Gallery 1.0.0 to 5.0.0 stay on npm and\nare never deprecated, because published core 0.25.0 depends on `^5.0.0`; the shared line cannot use those versions again\n(the first stable major of all six packages is 6.0.0; see [the release process](release-process.md)).\n\n### Theme variants (gallery 4)\n\nA theme record may list `variants`, an array of `{ "id", "name", "variant" }` whose `variant` is a value of `design.variant`\n(a background slot such as `"dark1"`, or `{ "background", "accents" }`; the same definition, `ColorVariant` in\n`opf.schema.json`, validates it). Gallery 4 gives `minimal`, `classic` and `bold` four each: `light`, `light-alt`, `dark`\nand `dark-alt`. A picker offers them and writes the chosen `variant` into `design.variant`; documents never store the id, and\nthe engines read only `design.variant`. Light and dark are therefore variants of every theme, and gallery 4 has no `dark`\ntheme: `design.theme: "dark"` is an unresolved reference (`opf/unresolved-reference`). Use a theme with a dark variant, for\nexample `{ "theme": "classic", "variant": "dark1" }`, and `colorScheme: "boost"` with `fontScheme: "seaford"` for the old\ndark look.\n\n## Endpoints\n\n| Request | Response |\n| --- | --- |\n| `GET https://www.pptx.gallery/<kind>/index.json` | Catalog index |\n| `GET https://www.pptx.gallery/<kind>` with `Accept: application/json` | Same catalog index |\n| `GET https://www.pptx.gallery/<kind>/<id>.json` | One published record |\n| `GET https://www.pptx.gallery/<kind>/<id>` with `Accept: application/json` | Same record |\n| Either URL from a browser | The gallery\'s HTML page |\n\nEngines never call these. A host may, to build the catalog it registers. Catalog files are served with\n`Access-Control-Allow-Origin: *`, and negotiated URLs send `Vary: Accept`. The older\n`https://www.pptx.gallery/api/<dimension>.json` envelopes carry the gallery\'s presentation data in its own shape; they\nare not OPF records and link their catalog index with `Link: <\u2026/<kind>/index.json>; rel="alternate"`.\n\n## Kinds\n\n| `<kind>` (URL and snapshot directory) | Key | Record schema | Role | Snapshot mode |\n| --- | --- | --- | --- | --- |\n| `audiences` | `audiences` | `opf-audience/v1` | content, `audience` | mirror |\n| `color-schemes` | `colorSchemes` | `opf-color-scheme/v1` | content, `design.colorScheme` | mirror |\n| `font-schemes` | `fontSchemes` | `opf-font-scheme/v1` | content, `design.fontScheme` | mirror |\n| `layouts` | `layouts` | `opf-layout/v2` since 0.19 | content, `Slide.layout` | authored in `packages/gallery` |\n| `narratives` | `narratives` | `opf-narrative/v1` | content, `narrative` | mirror |\n| `purposes` | `purposes` | `opf-purpose/v1` | content, `purpose` | mirror |\n| `themes` | `themes` | `opf-theme/v1` | content, `design.theme` | mirror |\n| `tones` | `tones` | `opf-tone/v1` | content, `tone` | mirror |\n| `chart-types` | `chartTypes` | `opf-chart-type/v1` | display metadata for `chart.type` | mirror |\n| `languages` | `languages` | `opf-language/v1` | display metadata for `language` | mirror |\n| `social-platforms` | `socialPlatforms` | `opf-social-platform/v1` | display metadata for `socials` keys | mirror |\n\nRecord schema ids are `https://openpresentation.org/schema/<name>`. A font scheme\'s `languages` list holds BCP-47 tags.\n\n## Index and record shape\n\nAn index validates against `spec/schemas/catalog-index.schema.json`:\n\n```json\n{\n "$schema": "https://openpresentation.org/schema/opf-catalog-index/v1",\n "kind": "tones",\n "version": "1",\n "description": "\u2026",\n "contentSha256": "b5a8d093\u2026",\n "records": [{ "id": "formal", "name": "Formal", "summary": "\u2026", "file": "formal.json" }]\n}\n```\n\n- `records` is in canonical order. `file` is relative to the index.\n- `version` is the index format version.\n- `contentSha256` is the lowercase hex SHA-256 of the canonical JSON of the full records in index order, with every\n top-level `x-*` member removed. Canonical JSON sorts object keys and has no insignificant whitespace (`canonicalJson()`\n in `scripts/catalog-snapshot.mjs`). The bundled index and the published index carry the same value for a mirrored kind.\n\nA **published record file** names its companion schema in `$schema` and carries its `id`; `validateCatalogRecord(kind,\nrecord)` checks one. Publishers may add top-level `x-*` members (pptx.gallery puts page URLs, mood tags, contrast\nnotes and font stacks in `x-gallery`); the snapshot never carries them. The copy a document embeds has neither\n`$schema` nor `id` nor `x-*` members.\n\nBefore v1 an id is renamed or removed outright: there are no deprecated records, aliases or redirects. A document that\nnames a retired id gets `opf/unresolved-reference`.\n\n## Historical snapshot (before 0.19)\n\nBefore 0.19, Core stored the snapshot at `spec/catalogs/manifest.json` (schema `spec/schemas/catalog-manifest.schema.json`):\n\n```json\n{\n "publisher": "https://www.pptx.gallery",\n "source": { "repository": "https://github.com/Data-Advantage/pptx-gallery", "commit": "<sha>", "path": "public" },\n "kinds": {\n "tones": { "mode": "mirror", "records": 7, "contentSha256": "\u2026", "gallery": { "records": 7, "contentSha256": "\u2026" } }\n }\n}\n```\n\nThat historical snapshot used `mirror` for every kind and held every record the gallery published (278 layouts at that checkpoint). The `packages/gallery` package owns the snapshot and the 30 v2 layout templates; core consumes the package, and the records are tracked in this repository. No browser entry of Core imports catalog records (`pnpm check:catalog-free`).\n\nThe 1.x design synchronized the snapshot from pptx.gallery and refused a publisher that stopped serving a held id\n(waived per id with `--allow-removed`). That sync was retired with gallery 2.0.0; a record is now removed by a pull\nrequest that deletes it and its index entry, and the stability check requires the removal to be declared and the release to be a minor version.\n\n### Updating it\n\nEdit records by pull request in `packages/gallery`: change the record and its index entry, run `pnpm --filter @openpresentation/gallery rehash` and `previews`, and add a `changes/<slug>.md` fragment (`packages: [gallery]`; a drawing change is declared with `drawing:`, see `changes/README.md`). The package is released with the other five under the repository\'s one shared version. `pnpm check:spec` and `pnpm check:catalog` verify the indexes, manifest and records, and `pnpm check:stability` holds a drawing change or a removed id to the declaration rule. Core\'s browser entries remain catalog-free (`pnpm check:catalog-free`).\n\n## Reconciliation status\n\nThe per-kind divergence between the gallery and this snapshot is in\n[`programs/font-fidelity-everywhere/ff-37-catalog-divergence.md`](programs/font-fidelity-everywhere/ff-37-catalog-divergence.md).\n'
69
+ "markdown": '# Catalogs and the gallery package\n\nOPF 0.15 keeps catalogs out of the engine. A document embeds every record it uses, hosts register the catalogs they\ntrust, and core ships no records. The **default catalog** is the npm package **`@openpresentation/gallery`**, authored in\n`packages/gallery` of this repository (RR-78, moved in by MR-23) and released with the other packages under one shared version;\npptx.gallery presents it and consumes the published package. Core and the CLI\ndepend on it and register it in Node; core has no `/catalog` subpath (removed in 0.19, with no alias).\n\nThis page is the contract: the document shape, the one resolution rule, how hosts register catalogs, the authoring\nhelpers, the diagnostics, and who authors and releases the gallery package. The design behind it is\n[0.15-design.md](programs/format-audit/0.15-design.md).\n\n## Catalogs in a document\n\n`catalogs` groups the records a document embeds by the catalog they came from. Inside a group, records are keyed by\nkind and then by id:\n\n```json\n"catalogs": {\n "default": {\n "source": "https://www.pptx.gallery",\n "layouts": { "two-column": { "name": "Two column", "regions": [{ "name": "title" }, { "direction": "row", "regions": [{ "name": "first", "accepts": ["text"], "flow": "auto" }, { "name": "second", "accepts": ["text"], "flow": "auto" }] }] } },\n "themes": { "minimal": { "name": "Minimal", "colorScheme": "cool-horizon", "fontScheme": "aptos" } }\n },\n "acme": { "source": "pkg:@acme/opf-catalog", "layouts": { "hero": { "name": "Hero", "regions": [{ "name": "title" }, { "name": "media", "accepts": ["image"], "flow": "none" }] } } },\n "custom": { "layouts": { "q4-special": { "name": "Q4 special", "regions": [{ "name": "title" }, { "name": "chart", "accepts": ["chart"], "flow": "none" }] } } }\n}\n```\n\n- **`default`** is the catalog bare ids come from. It needs a `source` when it holds records. Omitted: bare ids fall\n back to the host\'s default catalog. `"default": false`: no catalog fallback at all, so every bare id must be embedded.\n- **`custom`** holds the records the document defines itself. It has no source. Forking a catalog record copies it here\n under a new id, so the change of ownership is visible.\n- **Any other name** (lowercase kebab-case) is an extra catalog, identified by its `source`: an HTTPS URL or a `pkg:`\n package reference. The name is the prefix its references use (`acme:hero`).\n- The kinds are `layouts`, `themes`, `colorSchemes`, `fontSchemes`, `narratives`, `audiences`, `purposes` and `tones`.\n- An embedded record has the fields of its kind\'s companion schema without `$schema` and `id` (the key is the id), and\n without the catalog\'s `x-*` display metadata. `validate` checks each one against the companion schema\n (`opf/catalog-record`).\n\n## References\n\nEvery content reference is a bare `id` or `name:id`, and every prefix must name a group (`opf/undeclared-catalog`\notherwise, a format error that engines reject at their format check). URLs and `pkg:` strings are not references; a named group replaces them. The references are\n`Slide.layout`, `design.theme`, `design.colorScheme` (or its `id`), `design.fontScheme` (or its `id`), the same three on\n`Slide.design`, `narrative`, `audience`, `purpose` and `tone` (a string or an object\'s `id`), a theme record\'s\n`colorScheme` and `fontScheme`, and a `language` object\'s `fontScheme` and `googleFontScheme`. An `audience` or\n`purpose` string that is not an id (it has spaces, capitals or punctuation) is free text, never looked up.\n\n## Resolution\n\nOne rule for every kind; nothing is ever fetched:\n\n1. A bare id: `catalogs.custom`, then the records embedded under `catalogs.default`, then the catalog the host\n registered for `default.source` (the host\'s default catalog when `default` is omitted; nothing when it is `false`).\n2. `name:id`: the records embedded under `catalogs.<name>`, then the catalog the host registered for its `source`.\n3. A reference written inside an embedded record (a theme\'s `colorScheme`) resolves in that record\'s own group first,\n so acme\'s `brand` theme finds acme\'s `ocean` before the default catalog\'s.\n4. Otherwise the reference is **unresolved**: the `opf/unresolved-reference` warning names the reference and the\n source it was looked for in, a slide composes automatically, and a design uses the engine default. A strict export\n fails with the same reference.\n\nA record embedded under `catalogs.default` or a named group whose registered catalog has no record of that kind and id is the warning `opf/catalog-record-not-in-source`: it is the document\'s own record and belongs in `catalogs.custom`. The check is silent when no catalog is registered for the group\'s source, for a record the catalog has with other content (an update difference) and for `custom`; rendering is unchanged.\n\nA slide with no `layout` is automatic composition, not a missing reference, and has no finding. Engines never\nsubstitute a different layout for one that does not resolve.\n\n## Registering catalogs\n\nEvery entry point that resolves references takes the same option, `catalogs`: the catalogs the host registered,\nmatched by `source`.\n\n> **The host-default rule.** The first registered catalog is the default for documents that omit `catalogs.default`:\n> their bare ids resolve in `custom`, then in that catalog. A document whose `default` names a `source` uses the\n> registered catalog with that source, and nothing when the host did not register it, even if it registered others.\n> `"default": false` uses no catalog for bare ids. Without the `catalogs` option the browser build and every engine\n> register nothing, so only what the document embeds resolves; in Node, core\'s file API and `validate`, `stats`,\n> `paginate`, `embed` and `edit` register `gallery`, as the CLI does. Hosts (the CLI, the editor app, the sites)\n> register `@openpresentation/gallery`; libraries and engines pass the option through.\n\n```js\nimport { validate, resolveSlideContext, paginate, embed } from \'@openpresentation/opf\';\nimport { gallery } from \'@openpresentation/gallery\';\n\nconst catalogs = [gallery, acmeCatalog]; // acmeCatalog = { source: \'pkg:@acme/opf-catalog\', layouts: { hero: { \u2026 } } }\nvalidate(document, { catalogs });\nresolveSlideContext(document, 0, { catalogs, strictReferences: true }); // throws OPFUnresolvedReferenceError\npaginate(document, { catalogs });\n```\n\nA registered catalog has the shape of a document group with `source` required: `{ source, layouts?: { <id>: record },\nthemes?, \u2026 }`. Records may keep their `x-*` display metadata there; embedding strips it. `resolveSlideContext`,\n`paginate`, `validate`, `stats`, `resolveScriptFonts` (`/composition`), `resolveReference`, `catalogRecords`, `embed`,\n`copySlides`, `moveToCustom` and `updateFromCatalog` all take it, and opf-render, opf-pptx and opf-editor pass it through to core, so a\nrecord a host registers resolves the same in preview, editor and export. Without it only what the document embeds\nresolves. A `catalogs` value that is not an array of registered catalogs throws `OPFCatalogsOptionError`\n(`code: "invalid-catalogs"`) at every entry point. `resolveSlideContext(...).resolved.provenance` (and\n`resolveDesignRecords(...).provenance`) says where each resolved layout, theme, colour scheme and font scheme came from\n(`{ kind, reference, id, group, source?, origin }`, origin `document` or `host`), so engines record provenance without\nresolving again.\n\n`@openpresentation/gallery` exports:\n\n| Export | What it is |\n| --- | --- |\n| `gallery` | The content records, keyed by kind and id, registered under `GALLERY_SOURCE` |\n| `GALLERY_SOURCE` | `https://www.pptx.gallery` |\n| `CATALOG_SCHEMA` | The version of core\'s catalog record schemas the records target (also `opf.catalogSchema` in its `package.json`) |\n| `catalogDisplay` | Display metadata for pickers: `chartTypes`, `languages` and `socialPlatforms` by id |\n| `catalogIndexes` | Each kind\'s `index.json` |\n| `catalogManifest` | A content hash per kind, and the pptx.gallery commit of the one-time 1.x snapshot (never re-synced: records have been authored in the gallery package since 2.0.0) |\n\n`@openpresentation/gallery/previews` exports the static HTML layout previews: `layoutPreviews`, `layoutPreviewIndex`,\n`layoutPreviewSlugs`, `getLayoutPreview` and `hasLayoutPreview`.\n\nThe region-list source contract (FA-33) reads catalog record generation `CATALOG_SCHEMA = 4` (exported from core).\nThis package compatibility generation is separate from the layout schema URI: layout records retain\n`https://openpresentation.org/schema/opf-layout/v2` and per-kind `schemaVersion: 2`. Generation 4 refuses\ngeneration-3 Gallery records because `areas`, `columns`, `rows` and the `regions` map no longer validate; like generation 3\n(nested area arrays) it does not introduce a `/v3` schema URI.\n\nCore reads catalog schema `CATALOG_SCHEMA` (exported from its root) and the gallery declares the one its records target\n(`CATALOG_SCHEMA`, `opf.catalogSchema`). The seven packages release in lockstep at one version, so there is no runtime\ncheck (the former `gallery-schema-mismatch` refusal of the Node defaults and the CLI was removed in MR-62, with no alias):\n`pnpm check:catalog-schema` fails the build when the two constants differ. A third-party catalog still declares its own\ngeneration the same way. No browser entry of core imports catalog data; `pnpm check:catalog-free` holds that with an\nesbuild metafile budget.\n\nOPF 0.19 removed `@openpresentation/opf/catalog` (`defaultCatalog`, `DEFAULT_CATALOG_SOURCE`, `catalogDisplay`,\n`catalogIndexes` and the layout previews) and core\'s `spec/catalogs/` and `spec/previews/` files, with no alias: import\nthe same records from `@openpresentation/gallery`, where `defaultCatalog` is `gallery` and `DEFAULT_CATALOG_SOURCE` is\n`GALLERY_SOURCE`.\n\n## Authoring helpers\n\n- **`embed(document, { catalogs })`** embeds each referenced record once, in the group it resolves in, together with\n the records it references (a theme\'s colour and font schemes). A bare id from the host default goes under\n `catalogs.default` with the catalog\'s source. Records already embedded are kept as they are; `x-*`, `$schema` and `id`\n are stripped. It returns `{ document, added, unresolved }` and is idempotent. Authoring tools call it on save and\n export; `opf embed` does it on the command line.\n- **`copySlides(from, to, indexes, { catalogs, at })`** copies slides with their records. Groups match by `source`, not\n by name: references are rewritten to the target\'s name for that source, and a missing group is added (renamed\n `<name>-2` when its name is taken). A source document without `catalogs.default` inherits the host default\'s\n source; when the target\'s default has another source, or is `false`, that catalog is added to the target as a named\n group and the copied bare references get its prefix, so copied slides never change catalog. A record the target\n already has with the same content is reused. A `custom` id\n the target uses for different content is renamed `<id>-2`. A catalog record whose revision differs from the one the\n target resolves moves into `custom` as `<id>-2`, so the copied slides look the same, and is listed in `renamed`.\n `renamed` lists only the records a call creates under a new id: a later copy that reuses one is not a rename.\n- **`moveToCustom(document, { kind, reference }, { catalogs })`** moves a record embedded under `default` or a named\n group into `custom`: the fix `opf/catalog-record-not-in-source` suggests. Every reference that named it is rewritten\n to name it in `custom`, and its own references keep naming what they named. It keeps its id unless `custom` holds a\n different record under it (then `<id>-2` and up, reported in `renamed`; an identical one is reused). It returns\n `{ document, from, to, references, patch, renamed? }`, where `patch` is RFC 6902 for review and undo, and throws\n `OPFMoveToCustomError` (`invalid-reference`, `already-custom`, `not-embedded`, `invalid-id`). Fork:\n `moveToCustom(document, ref, { id })` copies the record into `custom` under the new id (`<id>-2` on conflict) and\n rewrites every reference to the copy, qualified references inside other records included, so the original is dropped\n from its group; an editor calls it on the first edit of a catalog record and applies the edit to the copy.\n- **`updateFromCatalog(document, catalogs, refs?)`** compares the records embedded under `default` and the named groups\n with the registered catalogs\' current ones and returns `{ changes, patch }`. Nothing changes until the author applies\n `patch` (`applyPatch`); `custom` records are never compared.\n- **`resolveReference`**, **`parseReference`** and **`catalogRecords`** (the records a picker can offer, with the\n reference to write for each) are the building blocks.\n\n## Engine vocabularies and engine defaults\n\nValues an engine must understand to draw are not catalog references:\n\n- `chart.type` is a schema enum;\n- the keys of `Organization.socials` and `Speaker.socials` are a schema enum, linked with URL patterns in code;\n- `language` is a BCP-47 tag (or a `Language` object with a `bcp47` tag); engines know the script, direction, OOXML\n culture tag and default script fonts of the vocabulary\'s tags and infer the script of any other well-formed tag.\n\nThey are listed in [`spec/reference/engine-vocabularies.json`](../spec/reference/engine-vocabularies.json) and exported\nas `CHART_TYPES`, `SOCIAL_PLATFORMS` and `LANGUAGES`. Their labels, descriptions and icons stay in the catalog as\ndisplay metadata (`catalogDisplay`).\n\nWhen nothing names a theme, colour scheme or font scheme, or a reference resolves nowhere, every engine draws with the\nsame engine defaults: [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json), exported as\n`ENGINE_DEFAULT_THEME`, `ENGINE_DEFAULT_COLOR_SCHEME` and `ENGINE_DEFAULT_FONT_SCHEME`. They are the drawing fields of\nthe gallery\'s `minimal`, `cool-horizon` and `aptos` records, compiled into code.\n\n## Ownership\n\n- **The gallery package owns every catalog record.** Since gallery 2.0.0 records, layouts and theme variants are authored\n by pull requests in `packages/gallery` (the product statement "pptx.gallery owns every default record" is retired, D11 of the\n monorepo program: the site presents the catalog, it does not author it); each id has one owner. Its\n display metadata lives under `x-*` members. pptx.gallery (the site, a separate repository) consumes the published package,\n adds its own presentation overlay and does not write records. A record that needs an engine change is one pull request,\n tested by one `verify` run.\n- **The gallery package owns the records and releases with the other packages.** `packages/gallery` is a tracked package of this repository (the monorepo program, D13): records, layouts and theme variants are authored by pull request here, its manifest holds a content hash per kind and the commit of the one-time 1.x snapshot from pptx.gallery, and it takes the repository\'s one shared version line (0.26.0 and later; gallery 1.0.0 to 5.0.0 stay on npm). The earlier `pnpm install` materializer of ignored fixtures is gone.\n- **A host chooses its catalog version.** Hosts register the catalog they choose; documents carry the records they were\n saved with. The packages are released together, so the pair a release ships (core and the gallery at the same version) is\n tested together.\n\n## The `@openpresentation/gallery` package\n\nRR-78: the same records are published as the npm package\n[`@openpresentation/gallery`](https://www.npmjs.com/package/@openpresentation/gallery) from `packages/gallery`, with the\nrepository\'s one shared version (from 0.26.0; the earlier independent line ended at 5.0.0). A record change is an ordinary\npull request (`catalog/<kind>/<id>.json` plus its index entry, then `pnpm rehash` and `pnpm previews` in the package, checked\nby `pnpm check:changed`); a `changes/<slug>.md` fragment records it and the release workflow publishes it with the rest:\n\n```js\nimport { gallery } from \'@openpresentation/gallery\';\nvalidate(document, { catalogs: [gallery] });\n```\n\n`gallery` replaces what `@openpresentation/opf/catalog` exported as `defaultCatalog` (`source` and the eight content kinds, records\nkeyed by id without `$schema`, `id` or `x-*`); the package also exports `GALLERY_SOURCE`, `CATALOG_SCHEMA`,\n`catalogDisplay`, `catalogIndexes` and `catalogManifest`, and `/previews`. New records go in any release; an id removed\nor renamed, a changed drawing or a record schema change is declared in the `changes/` fragment and makes the release a minor\none (a patch is refused), which the pixel-stability check (`pnpm check:stability`, against the `latest` release) enforces.\nFrom OPF 0.19 core and the CLI depend on it (`workspace:^` in this checkout, published as `^X.Y.Z` of the same release), core\'s\nNode defaults register `gallery`, and core drops `/catalog` and `spec/catalogs/`. Gallery 1.0.0 to 5.0.0 stay on npm and\nare never deprecated, because published core 0.25.0 depends on `^5.0.0`; the shared line cannot use those versions again\n(the first stable major of all six packages is 6.0.0; see [the release process](release-process.md)).\n\n### Theme variants (gallery 4)\n\nA theme record may list `variants`, an array of `{ "id", "name", "variant" }` whose `variant` is a value of `design.variant`\n(a background slot such as `"dark1"`, or `{ "background", "accents" }`; the same definition, `ColorVariant` in\n`opf.schema.json`, validates it). Gallery 4 gives `minimal`, `classic` and `bold` four each: `light`, `light-alt`, `dark`\nand `dark-alt`. A picker offers them and writes the chosen `variant` into `design.variant`; documents never store the id, and\nthe engines read only `design.variant`. Light and dark are therefore variants of every theme, and gallery 4 has no `dark`\ntheme: `design.theme: "dark"` is an unresolved reference (`opf/unresolved-reference`). Use a theme with a dark variant, for\nexample `{ "theme": "classic", "variant": "dark1" }`, and `colorScheme: "boost"` with `fontScheme: "seaford"` for the old\ndark look.\n\n## Endpoints\n\n| Request | Response |\n| --- | --- |\n| `GET https://www.pptx.gallery/<kind>/index.json` | Catalog index |\n| `GET https://www.pptx.gallery/<kind>` with `Accept: application/json` | Same catalog index |\n| `GET https://www.pptx.gallery/<kind>/<id>.json` | One published record |\n| `GET https://www.pptx.gallery/<kind>/<id>` with `Accept: application/json` | Same record |\n| Either URL from a browser | The gallery\'s HTML page |\n\nEngines never call these. A host may, to build the catalog it registers. Catalog files are served with\n`Access-Control-Allow-Origin: *`, and negotiated URLs send `Vary: Accept`. The older\n`https://www.pptx.gallery/api/<dimension>.json` envelopes carry the gallery\'s presentation data in its own shape; they\nare not OPF records and link their catalog index with `Link: <\u2026/<kind>/index.json>; rel="alternate"`.\n\n## Kinds\n\n| `<kind>` (URL and snapshot directory) | Key | Record schema | Role | Snapshot mode |\n| --- | --- | --- | --- | --- |\n| `audiences` | `audiences` | `opf-audience/v1` | content, `audience` | mirror |\n| `color-schemes` | `colorSchemes` | `opf-color-scheme/v1` | content, `design.colorScheme` | mirror |\n| `font-schemes` | `fontSchemes` | `opf-font-scheme/v1` | content, `design.fontScheme` | mirror |\n| `layouts` | `layouts` | `opf-layout/v2` since 0.19 | content, `Slide.layout` | authored in `packages/gallery` |\n| `narratives` | `narratives` | `opf-narrative/v1` | content, `narrative` | mirror |\n| `purposes` | `purposes` | `opf-purpose/v1` | content, `purpose` | mirror |\n| `themes` | `themes` | `opf-theme/v1` | content, `design.theme` | mirror |\n| `tones` | `tones` | `opf-tone/v1` | content, `tone` | mirror |\n| `chart-types` | `chartTypes` | `opf-chart-type/v1` | display metadata for `chart.type` | mirror |\n| `languages` | `languages` | `opf-language/v1` | display metadata for `language` | mirror |\n| `social-platforms` | `socialPlatforms` | `opf-social-platform/v1` | display metadata for `socials` keys | mirror |\n\nRecord schema ids are `https://openpresentation.org/schema/<name>`. A font scheme\'s `languages` list holds BCP-47 tags.\n\n## Index and record shape\n\nAn index validates against `spec/schemas/catalog-index.schema.json`:\n\n```json\n{\n "$schema": "https://openpresentation.org/schema/opf-catalog-index/v1",\n "kind": "tones",\n "version": "1",\n "description": "\u2026",\n "contentSha256": "b5a8d093\u2026",\n "records": [{ "id": "formal", "name": "Formal", "summary": "\u2026", "file": "formal.json" }]\n}\n```\n\n- `records` is in canonical order. `file` is relative to the index.\n- `version` is the index format version.\n- `contentSha256` is the lowercase hex SHA-256 of the canonical JSON of the full records in index order, with every\n top-level `x-*` member removed. Canonical JSON sorts object keys and has no insignificant whitespace (`canonicalJson()`\n in `scripts/catalog-snapshot.mjs`). The bundled index and the published index carry the same value for a mirrored kind.\n\nA **published record file** names its companion schema in `$schema` and carries its `id`; `validateCatalogRecord(kind,\nrecord)` checks one. Publishers may add top-level `x-*` members (pptx.gallery puts page URLs, mood tags, contrast\nnotes and font stacks in `x-gallery`); the snapshot never carries them. The copy a document embeds has neither\n`$schema` nor `id` nor `x-*` members.\n\nBefore v1 an id is renamed or removed outright: there are no deprecated records, aliases or redirects. A document that\nnames a retired id gets `opf/unresolved-reference`.\n\n## Historical snapshot (before 0.19)\n\nBefore 0.19, Core stored the snapshot at `spec/catalogs/manifest.json` (schema `spec/schemas/catalog-manifest.schema.json`):\n\n```json\n{\n "publisher": "https://www.pptx.gallery",\n "source": { "repository": "https://github.com/Data-Advantage/pptx-gallery", "commit": "<sha>", "path": "public" },\n "kinds": {\n "tones": { "mode": "mirror", "records": 7, "contentSha256": "\u2026", "gallery": { "records": 7, "contentSha256": "\u2026" } }\n }\n}\n```\n\nThat historical snapshot used `mirror` for every kind and held every record the gallery published (278 layouts at that checkpoint). The `packages/gallery` package owns the snapshot and the 30 v2 layout templates; core consumes the package, and the records are tracked in this repository. No browser entry of Core imports catalog records (`pnpm check:catalog-free`).\n\nThe 1.x design synchronized the snapshot from pptx.gallery and refused a publisher that stopped serving a held id\n(waived per id with `--allow-removed`). That sync was retired with gallery 2.0.0; a record is now removed by a pull\nrequest that deletes it and its index entry, and the stability check requires the removal to be declared and the release to be a minor version.\n\n### Updating it\n\nEdit records by pull request in `packages/gallery`: change the record and its index entry, run `pnpm --filter @openpresentation/gallery rehash` and `previews`, and add a `changes/<slug>.md` fragment (`packages: [gallery]`; a drawing change is declared with `drawing:`, see `changes/README.md`). The package is released with the other five under the repository\'s one shared version. `pnpm check:spec` and `pnpm check:catalog` verify the indexes, manifest and records, and `pnpm check:stability` holds a drawing change or a removed id to the declaration rule. Core\'s browser entries remain catalog-free (`pnpm check:catalog-free`).\n\n## Reconciliation status\n\nThe per-kind divergence between the gallery and this snapshot is in\n[`programs/font-fidelity-everywhere/ff-37-catalog-divergence.md`](programs/font-fidelity-everywhere/ff-37-catalog-divergence.md).\n'
70
70
  },
71
71
  {
72
72
  "slug": "design-resolution",
@@ -258,7 +258,7 @@ var docsData = Object.freeze([
258
258
  "slug": "node",
259
259
  "file": "docs/node.md",
260
260
  "title": "OPF files in Node: `@openpresentation/opf`",
261
- "markdown": '# OPF files in Node: `@openpresentation/opf`\n\n`@openpresentation/opf` is one import for every runtime. In Node (and Bun and Deno) it is core plus the file API: it converts,\nopens and saves OPF files, and draws them as PDF, PNG, SVG and PPTX through the optional engines. In a browser or a worker the\nsame import gives the browser-safe build, with the same names, where only the file functions refuse.\n\n```ts\nimport * as opf from "@openpresentation/opf";\n\nawait opf.convert("deck.opf.md", "deck.pdf");\nconst deck = await opf.open("deck.opf.md");\ndeck.slides.push({ title: "Q4" });\nawait opf.save(deck, "deck.opf.md");\nconst { files } = await opf.convert(deck, { format: "pptx" }); // bytes, nothing written\n```\n\nThe namespace is all of core (`validate`, `parse`, `stringify`, `paginate`, `parseSlideSelection`, the types and the error\nclasses), so one import covers an application. The pptx.gallery catalog is the package `@openpresentation/gallery` (`gallery`, a\ndependency of core, RR-78); in Node, `open`, `save` and `convert` register it when no `catalogs` are passed. A gallery that targets\nanother catalog record schema than core reads (`CATALOG_SCHEMA`) is refused with `gallery-schema-mismatch`.\n\n## The CLI\'s verbs, with the CLI\'s defaults\n\nOPF 0.18 gives the Node build the verbs of the `opf` command, so a script calls them instead of spawning `opf`:\n\n| Function | Command | In Node, when `catalogs` is omitted |\n| --- | --- | --- |\n| `validate(deck, options?)` | `opf validate` | the gallery is registered, so `opf validate deck` and `validate(deck)` report the same findings |\n| `stats(deck, options?)` | `opf stats` | the gallery |\n| `paginate(deck, options?)` | `opf paginate` | the gallery. Pass `fonts` (opf-render\'s `loadFonts()`) for measured page breaks; without it the result says `layout: "estimated"`. `opf paginate` measures with the office pack, prepared once per process, as `convert` does for an export with `paginate: true` |\n| `embed(deck, options?)` | `opf embed` | the gallery |\n| `edit(deck, patch, options?)` | `opf edit` | the gallery for the check. Applies a JSON Patch whole or not at all (`OPFPatchError`), checks the result (`OPFValidationError`; `validate: false` skips it) and returns `{ presentation, findings, inverse }` |\n| `fill(template, data?, options?)` | `opf fill` | no catalog: `data` is CSV, TSV or JSON text, a record or a list of records; returns `{ decks, complete, unfilled, diagnostics }`, and `presentation` with `combine: true`. `fillRecords(text)` reads the records alone |\n| `diff(a, b)`, `merge(base, ours, theirs)` | `opf diff`, `opf merge` | no catalog |\n\nAn explicit `catalogs: []` registers none. The browser build exports the same functions and registers no catalog (FA-21): a\nbrowser host passes `{ catalogs: [gallery] }` itself (`import { gallery } from "@openpresentation/gallery"`). Only `open`, `save` and `convert` are Node only.\n\nA shorter name is available as an npm alias, if you want one: `npm i opf@npm:@openpresentation/opf` installs the same package\nas `opf`, so `import * as opf from "opf"` works too. The docs keep the full name.\n\n## One import for every runtime\n\nOPF 0.18 removed the `@openpresentation/opf/node` subpath of 0.17: import `@openpresentation/opf` instead, with the same names\n(OPF 0.19 removed `@openpresentation/opf/catalog` and `defaultCatalog`, with no alias: import `gallery` from\n`@openpresentation/gallery`).\nThe package\'s conditional exports pick the build:\n\n| Condition | Build | `open`, `save`, `convert` |\n| --- | --- | --- |\n| `bun`, `deno`, `node` | the full build (`dist/index.js`) | work on files, and `convert` also returns bytes |\n| `workerd` (Cloudflare), `worker`, `browser`, `default` | the browser-safe build (`dist/browser.js`) | reject with `OPFApiError` code `node-only` |\n\nThe condition also decides how `validate` runs the JSON Schemas; see [Edge runtimes](#edge-runtimes-validators-that-never-compile-code).\n\n- **One type surface.** Both builds share `dist/index.d.ts` (the `types` condition, first in the map), so TypeScript sees the\n same declarations under `NodeNext` and `Bundler` resolution; the file functions are documented there as Node only. The\n declarations need no Node types.\n- **The browser-safe build** imports no Node builtin, no file system and neither engine (`scripts/check-browser-safe.mjs`\n bundles it for the browser and worker conditions). Its `node-only` messages name the alternative: `parse(text, { filename })`\n for `open`, `stringify(deck, { filename })` for `save`, and for `convert` the deck forms through `parse` and `stringify` and\n drawing through opf-render\'s `/export-browser` entry. The in-memory `convert(deck, { format })` is Node only in 0.18 too; a\n later release adds a browser implementation through `/export-browser`.\n- **The Node build** loads its file engine on the first file call, so importing the root costs no more than core.\n- **Bundlers** pick the browser build for a browser target and the full build for a Node target. A bundler that sets neither\n condition gets `default`, the browser-safe build: for a Node bundle, add the `node` condition (Rollup\'s node-resolve\n `exportConditions: ["node"]`, esbuild\'s `platform: "node"`).\n\n## Edge runtimes: validators that never compile code\n\n`validate` checks a document against the JSON Schemas with Ajv, and Ajv compiles each schema to JavaScript with `new Function` the\nfirst time it is used. Cloudflare Workers, Vercel Edge and a page under a Content-Security-Policy without `unsafe-eval` refuse\ncode generation from strings (Workers: "Code generation from strings disallowed for this context", while a request is handled), so\non those hosts the package ships the same validators compiled at build time (Ajv\'s standalone code generation, `scripts/validators-codegen.mjs`).\nThey give the same verdicts and the same findings: `test/precompiled-validators.test.mjs` compares them with Ajv on every example deck,\non catalog records and on random mutants of both, and on every `oneOf` and `anyOf` branch of the schemas.\n\nThe choice is made by the package\'s export conditions, through the internal `#schema-engine` import (package.json `imports`), so a\nhost needs no alias, no build step and no option:\n\n| Condition | Validators |\n| --- | --- |\n| `workerd` (Cloudflare Workers), `worker`, `edge-light` (Vercel Edge) | precompiled; no code is generated at runtime |\n| `node`, `bun`, `deno`, `browser`, `default` | Ajv, compiled on the first `validate` of the process |\n\nWrangler and OpenNext set `workerd`, `worker` and `browser` when they bundle a Worker, and the Edge runtime of Next.js sets `edge-light`,\nso a Worker needs nothing from you. Two cases do not get them by condition. Code that runs on a worker but was bundled for Node (a\nNext.js route handler that Turbopack or webpack resolved with the `node` condition before OpenNext packaged it, for one), and a browser\npage under a strict Content-Security-Policy. Import the precompiled validators once, before the first `validate`:\n\n```ts\nimport "@openpresentation/opf/precompiled"; // installs the build-time validators for this process; it exports nothing\n```\n\nThe price is size: the precompiled validators add about 78 KB gzipped (1.4 MB minified, mostly generated code) to the browser-safe\nbuild, which is why the browser keeps Ajv by default. In the package they are one minified chunk of `dist/` (1.5 MB, 121 KB gzipped;\nthe schemas are shared with the rest of the package, not copied), which adds about 125 KB to the tarball. On the first `validate` they save the time Ajv spends compiling the schemas\n(the first `validate` took about 100 ms with Ajv and about 10 ms precompiled in Node). A schema you compile yourself with Ajv is not covered: the precompiled build validates the OPF schemas that\nship in the package.\n\nThe `workerd` CI lane (`packages/javascript/test/workerd.test.mjs`, in the core job of `ci.yml`) bundles the package the way Wrangler\ndoes, runs it in the real `workerd` runtime from the `workerd` npm package (no Cloudflare account, no login), validates the example\ndecks, and requires the findings to equal Node\'s. A control in the same file bundles a Worker without the worker conditions and\nrequires workerd to refuse it, so the lane fails if the default validators go back to runtime compilation. Fonts, rasterizing and the\nin-memory `convert` are not part of this lane yet (CE-02, CE-03 in `docs/programs/cloud-editor/`).\n\n## The stack\n\nOPF is six packages. **Core** (`@openpresentation/opf`) is the format and its API: schemas, types, `validate`, `parse` and\n`stringify`, composition and pagination, and in Node the files. **The gallery** (`@openpresentation/gallery`) is the pptx.gallery\ncatalog, a dependency of core released on its own version line. **The CLI** (`@openpresentation/cli`) is the `opf` command;\nits `convert` command runs core\'s Node engine (through `@openpresentation/opf/internal/engine`,\nwhich the package exports under the `node` condition for the CLI only; it is not an application API). **opf-render**,\n**opf-pptx** and **opf-editor** are the engines: drawing (SVG, PNG, PDF), PowerPoint (export and import) and the editor.\n\n## Install\n\n```sh\nnpm install @openpresentation/opf @openpresentation/opf-render @openpresentation/opf-pptx \\\n @resvg/resvg-js sharp pdf-lib \\\n @expo-google-fonts/roboto@0.4.3 @expo-google-fonts/roboto-mono@0.4.2 @expo-google-fonts/caladea@0.4.2 \\\n @expo-google-fonts/arimo@0.4.3 @expo-google-fonts/tinos@0.4.2 @expo-google-fonts/cousine@0.4.3 \\\n @expo-google-fonts/gelasio@0.4.1 @expo-google-fonts/noto-sans@0.4.2\n```\n\n`@openpresentation/opf-render` and `@openpresentation/opf-pptx` are optional peer dependencies of core: npm does not install them\nfor you, and an application that only reads, validates or converts between deck forms needs neither. Core loads them the\nfirst time a call needs them, from core\'s own install location and then from the working directory; a missing one throws\n`peer-not-installed` with the install command. What each output needs is listed in [the CLI reference](cli.md#install): PNG\nneeds `@resvg/resvg-js` and `sharp`, raster PDF `pdf-lib`, pictures in a PDF `sharp`, every format the office font pack, and\n`.pptx` input or output `@openpresentation/opf-pptx`.\n\n## `convert(input, output, options?)`\n\n```ts\nawait opf.convert("deck.opf.md", "deck.pdf");\nawait opf.convert("deck.opf.md", "deck.pptx");\nawait opf.convert("deck.opf.md", "slides/deck.png"); // one PNG per slide: slides/deck-1.png, -2, ...\nawait opf.convert("deck.opf.md", "slides/slide-{n}.png"); // always numbered: slides/slide-1.png, slide-2.png, ...\nawait opf.convert("deck.pptx", "deck.opf.yaml"); // import\nawait opf.convert("deck.opf.json", "deck.opf.md"); // change the deck form\nawait opf.convert("deck.opf.md", "deck.png", { slides: "1-3", scale: 2 });\nawait opf.convert("deck.opf.md", "slides.zip", { format: "svg" });\n```\n\n`input` is a file path, a presentation object or the bytes of a `.pptx` file (`Uint8Array`). The formats come from the names:\n\n| Input | Read as |\n| --- | --- |\n| `.pptx`, PPTX bytes | imported through opf-pptx |\n| `.opf.md` | a Markdown deck |\n| `.yaml`, `.yml` | a YAML deck |\n| `.json` | a JSON deck |\n\n| Output | Written as |\n| --- | --- |\n| `.pdf`, `.pptx` | one file |\n| `.png`, `.svg` | one file per slide, named after the output: `slides/deck.png` gives `slides/deck-1.png`, `-2`, ... (the numbers are the deck\'s slide numbers, padded with zeros to the width of the largest number written: `deck-01.png` from ten slides, `deck-001.png` from a hundred; `slides: "2,5"` gives `deck-2.png` and `deck-5.png`). When one slide is selected (or the deck has one), it is written to the output name itself, as `opf convert deck.opf.json slide.png --slides 2` does. With `{n}` in the output (`"slides/slide-{n}.png"`) the files are always numbered, even for one slide, and `{n}` is the slide number padded by the same rule. `{n}` is the only placeholder: any other `{key}`, and `{n}` in a `.pdf`, `.pptx`, `.zip` or deck-form output, rejects with `invalid-option`. |\n| `.zip` | one archive of the slides, its entries named as the plain `.png` and `.svg` files above (`slides.zip` holds `slides-1.png`, ...; always numbered; the archive\'s own name takes no `{n}`). The slides are PNG unless `format: "svg"`. |\n| `.opf.md`, `.yaml`, `.yml`, `.json` | the deck in that form |\n\nAny other extension, and a plain `.md` file, rejects with `invalid-option`, which names the supported extensions. A `format`\nthat disagrees with the output\'s extension is refused too; it is only needed for a `.zip`.\n\n**Without an output path**, pass the format in the options: `convert(input, { format })` writes nothing and returns the files\nwith their names and bytes. `name` sets the base name of the files; without it they follow the deck: its `filename`, else its\nslugified `name`, else the input file\'s stem (`Q4-Review.pdf`, `Q4-Review-1.png`; padded like the files above, `Q4-Review-01.png` from ten slides, `deck.opf.yaml`). `zip: true` returns one archive of\nthe PNG or SVG slides; with an output path a `.zip` name makes the archive instead.\n\n**Options** are the flags of `opf convert` in camel case: `slides` (`3`, `"1,3-5"` or `[1, 3]`, read by `parseSlideSelection`), `includeHidden`, `paginate`, `scale`\n(0.1 to 8, PNG and raster PDF), `raster` (`true` draws a PDF as a picture per page; vector text is the default), `text` (an SVG\ncarries `"fonts"`, the default, which embeds the faces the slide uses; `"system"`, none, for a page that has the fonts; or\n`"paths"`, glyph outlines, so the file needs no font), `charts` (`"auto"`, `"native"` or `"picture"`), `provenance` and `images`\n(`"compatible"` or `"preserve"`) for PPTX, `date` (`YYYY-MM-DD` for date fields; nothing reads a clock), `catalogs` (the default\ncatalog when omitted), `fonts` (a prepared `loadFonts()` handle of `@openpresentation/opf-render/fonts-node`, or a folder or a list\nof folders of `.ttf`/`.otf` files), `assetDir`, plus `signals` (a `.pptx` input: also return the importer\'s per-shape\nsignals), and for output without a path `zip` and `name`, and `overwrite`. An option that does not apply to the pair rejects with\n`invalid-option`: `scale` for a deck output, `slides` for a PPTX, `raster` for anything but a PDF, `text` for anything but an SVG,\n`zip` or `name` with an output path. The 0.17 names (`pdfMode`, `svgFonts`, `chartex`, `imageFormat`, `fontDirs`, `filename`) were\nrenamed in 0.18 and are refused with the new name.\n\n**Writing.** Inputs are checked and all results are produced and staged before destination files are published. A preflight,\nrender or staging failure publishes no destination files, though parent folders may have been created. Each final file is\npublished atomically from a temporary sibling, so no partial file has the final name. Multiple files are not one transaction:\na later publication I/O failure can leave earlier complete outputs in place. Keep that possibility in your recovery logic.\nExisting regular outputs are replaced by default, as `save` and `fs.writeFile` do: running the same `convert` again rebuilds\nthe file. `overwrite: false` checks for existing outputs during preflight and reports `output-exists`, the rule the `opf`\ncommands apply without `--force`. A symlink, a directory or another non-regular file is refused (`output-not-file`). The result\nis `{ files: [{ name, path, type, bytes, ... }], findings }`, with the slide number, id and size of each picture and the pages of a PDF.\n\n**Images.** When `input` is a path, a deck\'s relative image paths resolve against the input file\'s folder, from any working\ndirectory; `assetDir` replaces that folder. Only image files inside it are read, and URLs are never fetched.\n\n**Chaining.** A `.pptx` input is imported, checked and then exported or written. `findings` holds both steps: the importer\'s\ndiagnostics carry the `import/` rule prefix, the format and references check `opf/`, and the engines `render/`, `pptx/`,\n`pdf/`, `fonts/` and `cli/`.\n\n## `open(pathOrBytes, options?)`\n\nReturns the presentation itself. A `.opf.md`, `.yaml`/`.yml` or `.json` file is read in the form its extension names and\nchecked for format and references; a `.pptx` path or its bytes are imported. An invalid deck throws `OPFValidationError`,\nwhose findings are located by line and column in the file; a PowerPoint file that cannot be imported throws `OPFImportError`.\nWarnings are not returned: `validate` reports them, and `convert("deck.pptx", "deck.opf.yaml")` returns what an import could\nnot keep. `catalogs` (default: the default catalog) is where references resolve.\n\n## `save(deck, path, options?)`\n\nWrites the deck in the form the file name names (`.opf.md`, `.yaml`/`.yml` or `.json`), atomically, creating folders and\nreplacing the file. It checks format and references first and refuses an invalid deck with `OPFValidationError`; `validate:\nfalse` writes work in progress, as an editor\'s autosave does (the YAML and Markdown writers still refuse a document that is\nnot well-formed OPF). `schemaComment: true` starts a YAML file with the editor modeline. Returns `{ path, format }`.\n\n## Errors\n\nEvery error has a `code`; branch on it, never on the message.\n\n| Class | Codes |\n| --- | --- |\n| `OPFApiError` (the base class) | `node-only` (the browser-safe build), `invalid-option`, `input-not-found`, `input-unreadable`, `invalid-presentation` (a deck written as a deck), `output-exists`, `output-not-file`, `output-unwritable` |\n| `OPFExportError` | the export step: `peer-not-installed` (the message carries the install command; `details` the package and range), `peer-too-old`, `peer-load-failed`, `invalid-option`, `invalid-presentation`, `no-slides`, `all-slides-hidden`, `export-failed`, a font code |\n| `OPFImportError` | the import step and `open` of a PowerPoint file: `peer-not-installed`, `peer-too-old`, `peer-load-failed`, `import-failed`, `invalid-presentation` |\n| `OPFValidationError` | `open` and `save` of an invalid deck; also thrown by core\'s `parse` and `assertValid` |\n\n`findings` on an error holds the located findings of the failed step.\n\n## Fonts, determinism and the network\n\nDrawing uses the renderer\'s bundled open font pack (the office pack with visual substitution) plus the files in the `fonts` folders, never\nsystem fonts. The pack is prepared once per process and reused by every call that passes no `fonts` (with `fonts` folders, once per\nlist of files); a deck that draws a script beyond Latin, Greek and Cyrillic gets a handle with its script\nfaces. Concurrent calls share the one preparation and draw one at a time on it. The output is the same bytes as with a handle\nof the caller\'s own. Nothing is fetched and no clock is read: the same input, options and installed versions give the same\nbytes on every machine.\n\n## Reading and writing text without files\n\nCore reads and writes deck text in every runtime: `parse(text, { filename?, format?, catalogs? })` returns the\npresentation and throws `OPFValidationError` on syntax, schema or reference errors (findings located by line and column);\n`stringify(deck, { format? | filename? })` returns text; `validate(text)` returns the full report, warnings included.\n\n## Slide selections\n\n`parseSlideSelection(selection, total, label?)` (both builds) turns a selection into slide numbers, one-based, ascending and\nwithout repeats: `3`, `[1, 3]`, `"1,3-5"`, `"2-"` (to the end) and `"-3"` (from the start). It throws `OPFApiError`\n`invalid-option` for a malformed selection, a number that is not a whole number from 1, a reversed range or a slide past the\nend, and `no-slides` when `total` is 0. `convert` and the opf CLI\'s `--slides` read selections with it, and an engine can share it.\n'
261
+ "markdown": '# OPF files in Node: `@openpresentation/opf`\n\n`@openpresentation/opf` is one import for every runtime. In Node (and Bun and Deno) it is core plus the file API: it converts,\nopens and saves OPF files, and draws them as PDF, PNG, SVG and PPTX through the optional engines. In a browser or a worker the\nsame import gives the browser-safe build, with the same names, where only the file functions refuse.\n\n```ts\nimport * as opf from "@openpresentation/opf";\n\nawait opf.convert("deck.opf.md", "deck.pdf");\nconst deck = await opf.open("deck.opf.md");\ndeck.slides.push({ title: "Q4" });\nawait opf.save(deck, "deck.opf.md");\nconst { files } = await opf.convert(deck, { format: "pptx" }); // bytes, nothing written\n```\n\nThe namespace is all of core (`validate`, `parse`, `stringify`, `paginate`, `parseSlideSelection`, the types and the error\nclasses), so one import covers an application. The pptx.gallery catalog is the package `@openpresentation/gallery` (`gallery`, a\ndependency of core, RR-78); in Node, `open`, `save` and `convert` register it when no `catalogs` are passed. Core and the gallery release together and\n`pnpm check:catalog-schema` holds their `CATALOG_SCHEMA` constants equal, so Node does not check it at runtime.\n\n## The CLI\'s verbs, with the CLI\'s defaults\n\nOPF 0.18 gives the Node build the verbs of the `opf` command, so a script calls them instead of spawning `opf`:\n\n| Function | Command | In Node, when `catalogs` is omitted |\n| --- | --- | --- |\n| `validate(deck, options?)` | `opf validate` | the gallery is registered, so `opf validate deck` and `validate(deck)` report the same findings |\n| `stats(deck, options?)` | `opf stats` | the gallery |\n| `paginate(deck, options?)` | `opf paginate` | the gallery. Pass `fonts` (opf-render\'s `loadFonts()`) for measured page breaks; without it the result says `layout: "estimated"`. `opf paginate` measures with the office pack, prepared once per process, as `convert` does for an export with `paginate: true` |\n| `embed(deck, options?)` | `opf embed` | the gallery |\n| `edit(deck, patch, options?)` | `opf edit` | the gallery for the check. Applies a JSON Patch whole or not at all (`OPFPatchError`), checks the result (`OPFValidationError`; `validate: false` skips it) and returns `{ presentation, findings, inverse }` |\n| `fill(template, data?, options?)` | `opf fill` | no catalog: `data` is CSV, TSV or JSON text, a record or a list of records; returns `{ decks, complete, unfilled, diagnostics }`, and `presentation` with `combine: true`. `fillRecords(text)` reads the records alone |\n| `diff(a, b)`, `merge(base, ours, theirs)` | `opf diff`, `opf merge` | no catalog |\n\nAn explicit `catalogs: []` registers none. The browser build exports the same functions and registers no catalog (FA-21): a\nbrowser host passes `{ catalogs: [gallery] }` itself (`import { gallery } from "@openpresentation/gallery"`). Only `open`, `save` and `convert` are Node only.\n\nA shorter name is available as an npm alias, if you want one: `npm i opf@npm:@openpresentation/opf` installs the same package\nas `opf`, so `import * as opf from "opf"` works too. The docs keep the full name.\n\n## One import for every runtime\n\nOPF 0.18 removed the `@openpresentation/opf/node` subpath of 0.17: import `@openpresentation/opf` instead, with the same names\n(OPF 0.19 removed `@openpresentation/opf/catalog` and `defaultCatalog`, with no alias: import `gallery` from\n`@openpresentation/gallery`).\nThe package\'s conditional exports pick the build:\n\n| Condition | Build | `open`, `save`, `convert` |\n| --- | --- | --- |\n| `bun`, `deno`, `node` | the full build (`dist/index.js`) | work on files, and `convert` also returns bytes |\n| `workerd` (Cloudflare), `worker`, `browser`, `default` | the browser-safe build (`dist/browser.js`) | reject with `OPFApiError` code `node-only` |\n\nThe condition also decides how `validate` runs the JSON Schemas; see [Edge runtimes](#edge-runtimes-validators-that-never-compile-code).\n\n- **One type surface.** Both builds share `dist/index.d.ts` (the `types` condition, first in the map), so TypeScript sees the\n same declarations under `NodeNext` and `Bundler` resolution; the file functions are documented there as Node only. The\n declarations need no Node types.\n- **The browser-safe build** imports no Node builtin, no file system and neither engine (`scripts/check-browser-safe.mjs`\n bundles it for the browser and worker conditions). Its `node-only` messages name the alternative: `parse(text, { filename })`\n for `open`, `stringify(deck, { filename })` for `save`, and for `convert` the deck forms through `parse` and `stringify` and\n drawing through opf-render\'s `/export-browser` entry. The in-memory `convert(deck, { format })` is Node only in 0.18 too; a\n later release adds a browser implementation through `/export-browser`.\n- **The Node build** loads its file engine on the first file call, so importing the root costs no more than core.\n- **Bundlers** pick the browser build for a browser target and the full build for a Node target. A bundler that sets neither\n condition gets `default`, the browser-safe build: for a Node bundle, add the `node` condition (Rollup\'s node-resolve\n `exportConditions: ["node"]`, esbuild\'s `platform: "node"`).\n\n## Edge runtimes: validators that never compile code\n\n`validate` checks a document against the JSON Schemas with Ajv, and Ajv compiles each schema to JavaScript with `new Function` the\nfirst time it is used. Cloudflare Workers, Vercel Edge and a page under a Content-Security-Policy without `unsafe-eval` refuse\ncode generation from strings (Workers: "Code generation from strings disallowed for this context", while a request is handled), so\non those hosts the package ships the same validators compiled at build time (Ajv\'s standalone code generation, `scripts/validators-codegen.mjs`).\nThey give the same verdicts and the same findings: `test/precompiled-validators.test.mjs` compares them with Ajv on every example deck,\non catalog records and on random mutants of both, and on every `oneOf` and `anyOf` branch of the schemas.\n\nThe choice is made by the package\'s export conditions, through the internal `#schema-engine` import (package.json `imports`), so a\nhost needs no alias, no build step and no option:\n\n| Condition | Validators |\n| --- | --- |\n| `workerd` (Cloudflare Workers), `worker`, `edge-light` (Vercel Edge) | precompiled; no code is generated at runtime |\n| `node`, `bun`, `deno`, `browser`, `default` | Ajv, compiled on the first `validate` of the process |\n\nWrangler and OpenNext set `workerd`, `worker` and `browser` when they bundle a Worker, and the Edge runtime of Next.js sets `edge-light`,\nso a Worker needs nothing from you. Two cases do not get them by condition. Code that runs on a worker but was bundled for Node (a\nNext.js route handler that Turbopack or webpack resolved with the `node` condition before OpenNext packaged it, for one), and a browser\npage under a strict Content-Security-Policy. Import the precompiled validators once, before the first `validate`:\n\n```ts\nimport "@openpresentation/opf/precompiled"; // installs the build-time validators for this process; it exports nothing\n```\n\nThe price is size: the precompiled validators add about 78 KB gzipped (1.4 MB minified, mostly generated code) to the browser-safe\nbuild, which is why the browser keeps Ajv by default. In the package they are one minified chunk of `dist/` (1.5 MB, 121 KB gzipped;\nthe schemas are shared with the rest of the package, not copied), which adds about 125 KB to the tarball. On the first `validate` they save the time Ajv spends compiling the schemas\n(the first `validate` took about 100 ms with Ajv and about 10 ms precompiled in Node). A schema you compile yourself with Ajv is not covered: the precompiled build validates the OPF schemas that\nship in the package.\n\nThe `workerd` CI lane (`packages/javascript/test/workerd.test.mjs`, in the core job of `ci.yml`) bundles the package the way Wrangler\ndoes, runs it in the real `workerd` runtime from the `workerd` npm package (no Cloudflare account, no login), validates the example\ndecks, and requires the findings to equal Node\'s. A control in the same file bundles a Worker without the worker conditions and\nrequires workerd to refuse it, so the lane fails if the default validators go back to runtime compilation. Fonts, rasterizing and the\nin-memory `convert` are not part of this lane yet (CE-02, CE-03 in `docs/programs/cloud-editor/`).\n\n## The stack\n\nOPF is six packages. **Core** (`@openpresentation/opf`) is the format and its API: schemas, types, `validate`, `parse` and\n`stringify`, composition and pagination, and in Node the files. **The gallery** (`@openpresentation/gallery`) is the pptx.gallery\ncatalog, a dependency of core released on its own version line. **The CLI** (`@openpresentation/cli`) is the `opf` command;\nits `convert` command runs core\'s Node engine (through `@openpresentation/opf/internal/engine`,\nwhich the package exports under the `node` condition for the CLI only; it is not an application API). **opf-render**,\n**opf-pptx** and **opf-editor** are the engines: drawing (SVG, PNG, PDF), PowerPoint (export and import) and the editor.\n\n## Install\n\n```sh\nnpm install @openpresentation/opf @openpresentation/opf-render @openpresentation/opf-pptx \\\n @resvg/resvg-js sharp pdf-lib \\\n @expo-google-fonts/roboto@0.4.3 @expo-google-fonts/roboto-mono@0.4.2 @expo-google-fonts/caladea@0.4.2 \\\n @expo-google-fonts/arimo@0.4.3 @expo-google-fonts/tinos@0.4.2 @expo-google-fonts/cousine@0.4.3 \\\n @expo-google-fonts/gelasio@0.4.1 @expo-google-fonts/noto-sans@0.4.2\n```\n\n`@openpresentation/opf-render` and `@openpresentation/opf-pptx` are optional peer dependencies of core: npm does not install them\nfor you, and an application that only reads, validates or converts between deck forms needs neither. Core loads them the\nfirst time a call needs them, from core\'s own install location and then from the working directory; a missing one throws\n`peer-not-installed` with the install command. What each output needs is listed in [the CLI reference](cli.md#install): PNG\nneeds `@resvg/resvg-js` and `sharp`, raster PDF `pdf-lib`, pictures in a PDF `sharp`, every format the office font pack, and\n`.pptx` input or output `@openpresentation/opf-pptx`.\n\n## `convert(input, output, options?)`\n\n```ts\nawait opf.convert("deck.opf.md", "deck.pdf");\nawait opf.convert("deck.opf.md", "deck.pptx");\nawait opf.convert("deck.opf.md", "slides/deck.png"); // one PNG per slide: slides/deck-1.png, -2, ...\nawait opf.convert("deck.opf.md", "slides/slide-{n}.png"); // always numbered: slides/slide-1.png, slide-2.png, ...\nawait opf.convert("deck.pptx", "deck.opf.yaml"); // import\nawait opf.convert("deck.opf.json", "deck.opf.md"); // change the deck form\nawait opf.convert("deck.opf.md", "deck.png", { slides: "1-3", scale: 2 });\nawait opf.convert("deck.opf.md", "slides.zip", { format: "svg" });\n```\n\n`input` is a file path, a presentation object or the bytes of a `.pptx` file (`Uint8Array`). The formats come from the names:\n\n| Input | Read as |\n| --- | --- |\n| `.pptx`, PPTX bytes | imported through opf-pptx |\n| `.opf.md` | a Markdown deck |\n| `.yaml`, `.yml` | a YAML deck |\n| `.json` | a JSON deck |\n\n| Output | Written as |\n| --- | --- |\n| `.pdf`, `.pptx` | one file |\n| `.png`, `.svg` | one file per slide, named after the output: `slides/deck.png` gives `slides/deck-1.png`, `-2`, ... (the numbers are the deck\'s slide numbers, padded with zeros to the width of the largest number written: `deck-01.png` from ten slides, `deck-001.png` from a hundred; `slides: "2,5"` gives `deck-2.png` and `deck-5.png`). When one slide is selected (or the deck has one), it is written to the output name itself, as `opf convert deck.opf.json slide.png --slides 2` does. With `{n}` in the output (`"slides/slide-{n}.png"`) the files are always numbered, even for one slide, and `{n}` is the slide number padded by the same rule. `{n}` is the only placeholder: any other `{key}`, and `{n}` in a `.pdf`, `.pptx`, `.zip` or deck-form output, rejects with `invalid-option`. |\n| `.zip` | one archive of the slides, its entries named as the plain `.png` and `.svg` files above (`slides.zip` holds `slides-1.png`, ...; always numbered; the archive\'s own name takes no `{n}`). The slides are PNG unless `format: "svg"`. |\n| `.opf.md`, `.yaml`, `.yml`, `.json` | the deck in that form |\n\nAny other extension, and a plain `.md` file, rejects with `invalid-option`, which names the supported extensions. A `format`\nthat disagrees with the output\'s extension is refused too; it is only needed for a `.zip`.\n\n**Without an output path**, pass the format in the options: `convert(input, { format })` writes nothing and returns the files\nwith their names and bytes. `name` sets the base name of the files; without it they follow the deck: its `filename`, else its\nslugified `name`, else the input file\'s stem (`Q4-Review.pdf`, `Q4-Review-1.png`; padded like the files above, `Q4-Review-01.png` from ten slides, `deck.opf.yaml`). `zip: true` returns one archive of\nthe PNG or SVG slides; with an output path a `.zip` name makes the archive instead.\n\n**Options** are the flags of `opf convert` in camel case: `slides` (`3`, `"1,3-5"` or `[1, 3]`, read by `parseSlideSelection`), `includeHidden`, `paginate`, `scale`\n(0.1 to 8, PNG and raster PDF), `raster` (`true` draws a PDF as a picture per page; vector text is the default), `text` (an SVG\ncarries `"fonts"`, the default, which embeds the faces the slide uses; `"system"`, none, for a page that has the fonts; or\n`"paths"`, glyph outlines, so the file needs no font), `charts` (`"auto"`, `"native"` or `"picture"`), `provenance` and `images`\n(`"compatible"` or `"preserve"`) for PPTX, `date` (`YYYY-MM-DD` for date fields; nothing reads a clock), `catalogs` (the default\ncatalog when omitted), `fonts` (a prepared `loadFonts()` handle of `@openpresentation/opf-render/fonts-node`, or a folder or a list\nof folders of `.ttf`/`.otf` files), `assetDir`, plus `signals` (a `.pptx` input: also return the importer\'s per-shape\nsignals), and for output without a path `zip` and `name`, and `overwrite`. An option that does not apply to the pair rejects with\n`invalid-option`: `scale` for a deck output, `slides` for a PPTX, `raster` for anything but a PDF, `text` for anything but an SVG,\n`zip` or `name` with an output path. The 0.17 names (`pdfMode`, `svgFonts`, `chartex`, `imageFormat`, `fontDirs`, `filename`) were\nrenamed in 0.18 and are refused with the new name.\n\n**Writing.** Inputs are checked and all results are produced and staged before destination files are published. A preflight,\nrender or staging failure publishes no destination files, though parent folders may have been created. Each final file is\npublished atomically from a temporary sibling, so no partial file has the final name. Multiple files are not one transaction:\na later publication I/O failure can leave earlier complete outputs in place. Keep that possibility in your recovery logic.\nExisting regular outputs are replaced by default, as `save` and `fs.writeFile` do: running the same `convert` again rebuilds\nthe file. `overwrite: false` checks for existing outputs during preflight and reports `output-exists`, the rule the `opf`\ncommands apply without `--force`. A symlink, a directory or another non-regular file is refused (`output-not-file`). The result\nis `{ files: [{ name, path, type, bytes, ... }], findings }`, with the slide number, id and size of each picture and the pages of a PDF.\n\n**Images.** When `input` is a path, a deck\'s relative image paths resolve against the input file\'s folder, from any working\ndirectory; `assetDir` replaces that folder. Only image files inside it are read, and URLs are never fetched.\n\n**Chaining.** A `.pptx` input is imported, checked and then exported or written. `findings` holds both steps: the importer\'s\ndiagnostics carry the `import/` rule prefix, the format and references check `opf/`, and the engines `render/`, `pptx/`,\n`pdf/`, `fonts/` and `cli/`.\n\n## `open(pathOrBytes, options?)`\n\nReturns the presentation itself. A `.opf.md`, `.yaml`/`.yml` or `.json` file is read in the form its extension names and\nchecked for format and references; a `.pptx` path or its bytes are imported. An invalid deck throws `OPFValidationError`,\nwhose findings are located by line and column in the file; a PowerPoint file that cannot be imported throws `OPFImportError`.\nWarnings are not returned: `validate` reports them, and `convert("deck.pptx", "deck.opf.yaml")` returns what an import could\nnot keep. `catalogs` (default: the default catalog) is where references resolve.\n\n## `save(deck, path, options?)`\n\nWrites the deck in the form the file name names (`.opf.md`, `.yaml`/`.yml` or `.json`), atomically, creating folders and\nreplacing the file. It checks format and references first and refuses an invalid deck with `OPFValidationError`; `validate:\nfalse` writes work in progress, as an editor\'s autosave does (the YAML and Markdown writers still refuse a document that is\nnot well-formed OPF). `schemaComment: true` starts a YAML file with the editor modeline. Returns `{ path, format }`.\n\n## Errors\n\nEvery error has a `code`; branch on it, never on the message.\n\n| Class | Codes |\n| --- | --- |\n| `OPFApiError` (the base class) | `node-only` (the browser-safe build), `invalid-option`, `input-not-found`, `input-unreadable`, `invalid-presentation` (a deck written as a deck), `output-exists`, `output-not-file`, `output-unwritable` |\n| `OPFExportError` | the export step: `peer-not-installed` (the message carries the install command; `details` the package and range), `peer-too-old`, `peer-load-failed`, `invalid-option`, `invalid-presentation`, `no-slides`, `all-slides-hidden`, `export-failed`, a font code |\n| `OPFImportError` | the import step and `open` of a PowerPoint file: `peer-not-installed`, `peer-too-old`, `peer-load-failed`, `import-failed`, `invalid-presentation` |\n| `OPFValidationError` | `open` and `save` of an invalid deck; also thrown by core\'s `parse` and `assertValid` |\n\n`findings` on an error holds the located findings of the failed step.\n\n## Fonts, determinism and the network\n\nDrawing uses the renderer\'s bundled open font pack (the office pack with visual substitution) plus the files in the `fonts` folders, never\nsystem fonts. The pack is prepared once per process and reused by every call that passes no `fonts` (with `fonts` folders, once per\nlist of files); a deck that draws a script beyond Latin, Greek and Cyrillic gets a handle with its script\nfaces. Concurrent calls share the one preparation and draw one at a time on it. The output is the same bytes as with a handle\nof the caller\'s own. Nothing is fetched and no clock is read: the same input, options and installed versions give the same\nbytes on every machine.\n\n## Reading and writing text without files\n\nCore reads and writes deck text in every runtime: `parse(text, { filename?, format?, catalogs? })` returns the\npresentation and throws `OPFValidationError` on syntax, schema or reference errors (findings located by line and column);\n`stringify(deck, { format? | filename? })` returns text; `validate(text)` returns the full report, warnings included.\n\n## Slide selections\n\n`parseSlideSelection(selection, total, label?)` (both builds) turns a selection into slide numbers, one-based, ascending and\nwithout repeats: `3`, `[1, 3]`, `"1,3-5"`, `"2-"` (to the end) and `"-3"` (from the start). It throws `OPFApiError`\n`invalid-option` for a malformed selection, a number that is not a whole number from 1, a reversed range or a slide past the\nend, and `no-slides` when `total` is 0. `convert` and the opf CLI\'s `--slides` read selections with it, and an engine can share it.\n'
262
262
  },
263
263
  {
264
264
  "slug": "numbered-lists",
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { SchemaEntry, SchemaName, audience, chartType, colorScheme, fontScheme, language, layout, narrative, presentation, purpose, schemaEntries, schemaNames, schemas, socialPlatform, theme, tone } from './schemas.js';
2
- import { a3 as ConvertInput, a4 as ConvertOptions, a5 as ConvertResult, a6 as ConvertFormat, a7 as OpenOptions, a8 as SaveOptions, a9 as SaveResult } from './conversion-jdVnvct4.js';
3
- export { aa as ConvertedFile, h as ExportFormat, F as FontsHandle, O as OPFApiError, n as OPFExportError, o as OPFImportError, S as SlideSelection, U as parseSlideSelection } from './conversion-jdVnvct4.js';
2
+ import { a3 as ConvertInput, a4 as ConvertOptions, a5 as ConvertResult, a6 as ConvertFormat, a7 as OpenOptions, a8 as SaveOptions, a9 as SaveResult } from './conversion-Bh-skyMc.js';
3
+ export { aa as ConvertedFile, h as ExportFormat, F as FontsHandle, O as OPFApiError, n as OPFExportError, o as OPFImportError, S as SlideSelection, U as parseSlideSelection } from './conversion-Bh-skyMc.js';
4
4
  export { R as RuleExplanation, e as explain } from './explain-iZeNOUc6.js';
5
5
  import { validate as validate$1 } from './validator.js';
6
6
  export { DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, validateCatalogRecord, validationRules } from './validator.js';
@@ -15,7 +15,7 @@ import { a as CatalogKind, U as UnresolvedReferenceDiagnostic, b as CatalogOptio
15
15
  export { c as CATALOG_GROUP_PATTERN, d as CATALOG_REFERENCE_PATTERN, e as CatalogRecordMap, f as CatalogRecords, g as CatalogReferenceSite, O as OPFCatalogsOptionError, h as OPFUnresolvedReferenceError, i as ResolvedReference, j as catalogGroupSource, k as catalogKinds, l as catalogRecords, m as catalogReferenceSites, p as parseReference, r as resolveReference, u as unresolvedReference } from './catalog-refs-kAefsZWT.js';
16
16
  import { a as Finding, J as JsonPatchOperation } from './finding-l9dlQ2NF.js';
17
17
  export { d as FindingCategory, e as FindingFix, f as FindingFocus, b as FindingLocation, F as FindingReport, c as FindingSeverity, g as FindingSuggestion, V as ValidationIssue } from './finding-l9dlQ2NF.js';
18
- export { a as CATALOG_SCHEMA, b as CatalogDisplayKind, C as CatalogRecordKind, c as catalogDisplayKinds, d as catalogSchemaNames } from './catalog-schemas-CC1d_h8s.js';
18
+ export { a as CATALOG_SCHEMA, b as CatalogDisplayKind, C as CatalogRecordKind, c as catalogDisplayKinds, d as catalogSchemaNames } from './catalog-schemas-LdqOPebq.js';
19
19
  import { paginate as paginate$1 } from './pagination.js';
20
20
  export { O as OPFPaginationError, P as PaginatedPage, c as PaginationMapping, d as PaginationOptions, e as PaginationResult, a as PresentationPaginationOptions, b as PresentationPaginationResult, S as SlideContext, f as SlideContextDiagnostic, g as SlideContextOptions, h as paginateSlide, r as resolveSlideContext } from './pagination-DyAKYpXu.js';
21
21
  export { ApplyPatchOptions, OPFPatchError, OPFPatchHashError, OPFPatchValidationError, PatchResult, applyPatch, documentHash, idToken, isIdToken, resolveIdPatch, resolveIdPointer, toIdPatch, toIdPointer } from './patch.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { defaultCatalogs } from './chunk-Z7XNHIW7.js';
1
+ import { defaultCatalogs } from './chunk-AQEAZDL2.js';
2
2
  import { stats, embed, edit, OPFApiError, asApiError } from './chunk-I6G32TB5.js';
3
3
  export { OPFApiError, OPFExportError, OPFImportError, OPFMoveToCustomError, copySlides, fill, fillRecords, localAssetSources, moveToCustom, parseSlideSelection, sameRecord, updateFromCatalog } from './chunk-I6G32TB5.js';
4
4
  export { specFileEntries, specFileKinds, specFilePaths } from './chunk-UJSAR6GF.js';
@@ -36,7 +36,7 @@ var stats2 = (presentation2, options) => stats(presentation2, withDefaultCatalog
36
36
  var paginate2 = (input, options) => paginate(input, withDefaultCatalog(options));
37
37
  var embed2 = (document, options) => embed(document, withDefaultCatalog(options));
38
38
  var edit2 = (deck, patch, options) => edit(deck, patch, withDefaultCatalog(options));
39
- var engine = () => import('./conversion-FUOYO726.js');
39
+ var engine = () => import('./conversion-7OPM5MMJ.js');
40
40
  var rethrow = (error, fallback) => {
41
41
  if (error instanceof OPFValidationError) throw error;
42
42
  throw asApiError(OPFApiError, error, fallback);
@@ -1,6 +1,6 @@
1
1
  export { b as DeckReport, c as DeckReportOptions, r as readDeckReport } from './deck-report-DQMsvyBI.js';
2
- import { F as FontsHandle, E as EmbeddedFace, R as Renderer, a as Reporter, P as Peer, b as PptxModule, O as OPFApiError } from './conversion-jdVnvct4.js';
3
- export { C as ConversionPlan, D as Diagnostic, c as EXPORT_FORMATS, d as EXPORT_TEXT, e as ExportContext, f as ExportFile, g as ExportFontSummary, h as ExportFormat, i as ExportOptions, j as ExportResult, k as ExportRun, l as ExportText, I as ImportOptions, m as ImportResult, n as OPFExportError, o as OPFImportError, p as PEER_RANGES, q as PPTX_PACKAGE, r as PreparedExport, s as RENDER_EXTRAS, t as RENDER_PACKAGE, u as ReportFinding, v as ResolvedExportOptions, S as SlideSelection, w as Source, V as VERSIONS, x as asApiError, y as assetRoot, z as checkDate, A as checkOptions, B as checkRenamedOptions, G as checkScale, H as deckFileFormat, J as exportFormatOf, K as finishReport, L as importPresentation, M as loadPptx, N as loadRenderer, Q as locatePackage, T as missingPeerFrom, U as parseSlideSelection, W as planConversion, X as prepareExport, Y as reaches, Z as readInput, _ as reportThrown, $ as resolveExportOptions, a0 as runExport, a1 as runPreparedExport, a2 as writePlanned } from './conversion-jdVnvct4.js';
2
+ import { F as FontsHandle, E as EmbeddedFace, R as Renderer, a as Reporter, P as Peer, b as PptxModule, O as OPFApiError } from './conversion-Bh-skyMc.js';
3
+ export { C as ConversionPlan, D as Diagnostic, c as EXPORT_FORMATS, d as EXPORT_TEXT, e as ExportContext, f as ExportFile, g as ExportFontSummary, h as ExportFormat, i as ExportOptions, j as ExportResult, k as ExportRun, l as ExportText, I as ImportOptions, m as ImportResult, n as OPFExportError, o as OPFImportError, p as PEER_RANGES, q as PPTX_PACKAGE, r as PreparedExport, s as RENDER_EXTRAS, t as RENDER_PACKAGE, u as ReportFinding, v as ResolvedExportOptions, S as SlideSelection, w as Source, V as VERSIONS, x as asApiError, y as assetRoot, z as checkDate, A as checkOptions, B as checkRenamedOptions, G as checkScale, H as deckFileFormat, J as exportFormatOf, K as finishReport, L as importPresentation, M as loadPptx, N as loadRenderer, Q as locatePackage, T as missingPeerFrom, U as parseSlideSelection, W as planConversion, X as prepareExport, Y as reaches, Z as readInput, _ as reportThrown, $ as resolveExportOptions, a0 as runExport, a1 as runPreparedExport, a2 as writePlanned } from './conversion-Bh-skyMc.js';
4
4
  import { Catalog } from './composition.js';
5
5
  import { Fonts } from './composition.js';
6
6
  import { PresentationPaginationResult } from './index.js';
@@ -167,10 +167,8 @@ interface MeasuringFonts {
167
167
  */
168
168
  declare function measuringFonts(deck: unknown, options: MeasuringFontsOptions): Promise<MeasuringFonts>;
169
169
 
170
- /** Why a gallery that declares `declared` cannot be the default catalog of a core that reads `reads`, or undefined when it can. */
171
- declare function galleryMismatch(declared: unknown, reads?: number): string | undefined;
172
- /** The catalogs a call registers when it names none: `[gallery]`. Throws `gallery-schema-mismatch` for a gallery of another schema. */
170
+ /** The catalogs a call registers when it names none: `[gallery]`. */
173
171
  declare function defaultCatalogs(): readonly Catalog[];
174
172
 
175
- export { FontsHandle, OPFApiError, Peer, PptxModule, Renderer, Reporter, checkDestinations, checkOutputPattern, createZip, deckStem, defaultCatalogs, embeddedFor, galleryMismatch, leaseSharedFonts, listFontDirectories, localAssetProblems, measuringFonts, padNumber, paginateDeck, pointerOf, prepareFonts, publishNew, runImport, stemOf, writeFiles };
173
+ export { FontsHandle, OPFApiError, Peer, PptxModule, Renderer, Reporter, checkDestinations, checkOutputPattern, createZip, deckStem, defaultCatalogs, embeddedFor, leaseSharedFonts, listFontDirectories, localAssetProblems, measuringFonts, padNumber, paginateDeck, pointerOf, prepareFonts, publishNew, runImport, stemOf, writeFiles };
176
174
  export type { AssetProblem, ImportRun, MeasuringFonts, MeasuringFontsOptions, PaginateDeckOptions, PaginatedDeck, PlannedFile };
@@ -1,7 +1,7 @@
1
- import { loadRenderer, listFontDirectories, leaseSharedFonts, prepareFonts } from './chunk-AFYXG3IB.js';
2
- export { EXPORT_FORMATS, EXPORT_TEXT, PEER_RANGES, PPTX_PACKAGE, RENDER_EXTRAS, RENDER_PACKAGE, Reporter, VERSIONS, assetRoot, checkDate, checkDestinations, checkOptions, checkOutputPattern, checkRenamedOptions, checkScale, createZip, deckFileFormat, deckStem, embeddedFor, exportFormatOf, finishReport, importPresentation, leaseSharedFonts, listFontDirectories, loadPptx, loadRenderer, localAssetProblems, locatePackage, missingPeerFrom, padNumber, planConversion, pointerOf, prepareExport, prepareFonts, publishNew, reaches, readInput, reportThrown, resolveExportOptions, runExport, runImport, runPreparedExport, stemOf, writeFiles, writePlanned } from './chunk-AFYXG3IB.js';
3
- import { defaultCatalogs } from './chunk-Z7XNHIW7.js';
4
- export { defaultCatalogs, galleryMismatch } from './chunk-Z7XNHIW7.js';
1
+ import { loadRenderer, listFontDirectories, leaseSharedFonts, prepareFonts } from './chunk-XSCZZWYI.js';
2
+ export { EXPORT_FORMATS, EXPORT_TEXT, PEER_RANGES, PPTX_PACKAGE, RENDER_EXTRAS, RENDER_PACKAGE, Reporter, VERSIONS, assetRoot, checkDate, checkDestinations, checkOptions, checkOutputPattern, checkRenamedOptions, checkScale, createZip, deckFileFormat, deckStem, embeddedFor, exportFormatOf, finishReport, importPresentation, leaseSharedFonts, listFontDirectories, loadPptx, loadRenderer, localAssetProblems, locatePackage, missingPeerFrom, padNumber, planConversion, pointerOf, prepareExport, prepareFonts, publishNew, reaches, readInput, reportThrown, resolveExportOptions, runExport, runImport, runPreparedExport, stemOf, writeFiles, writePlanned } from './chunk-XSCZZWYI.js';
3
+ import { defaultCatalogs } from './chunk-AQEAZDL2.js';
4
+ export { defaultCatalogs } from './chunk-AQEAZDL2.js';
5
5
  import { OPFApiError } from './chunk-I6G32TB5.js';
6
6
  export { OPFApiError, OPFExportError, OPFImportError, asApiError, parseSlideSelection } from './chunk-I6G32TB5.js';
7
7
  import './chunk-UJSAR6GF.js';
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as CatalogRecordKind } from './catalog-schemas-CC1d_h8s.js';
1
+ import { C as CatalogRecordKind } from './catalog-schemas-LdqOPebq.js';
2
2
  import { SchemaName } from './schemas.js';
3
3
  import './catalog-refs-kAefsZWT.js';
4
4
  import './json-BFSdESOE.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Canonical Open Presentation Format schemas, TypeScript types, local validation and composition; in Node, the file API and the CLI defaults register the pptx.gallery catalog of @openpresentation/gallery.",
5
5
  "license": "MIT",
6
6
  "author": "Open Presentation",
@@ -164,7 +164,7 @@
164
164
  "ajv-formats": "^3.0.1",
165
165
  "jsonc-parser": "3.3.1",
166
166
  "yaml": "^2.9.1",
167
- "@openpresentation/gallery": "^0.30.0"
167
+ "@openpresentation/gallery": "^0.31.0"
168
168
  },
169
169
  "devDependencies": {
170
170
  "@expo-google-fonts/arimo": "0.4.3",
@@ -185,8 +185,8 @@
185
185
  "workerd": "1.20261023.1"
186
186
  },
187
187
  "peerDependencies": {
188
- "@openpresentation/opf-pptx": "^0.30.0",
189
- "@openpresentation/opf-render": "^0.30.0"
188
+ "@openpresentation/opf-pptx": "^0.31.0",
189
+ "@openpresentation/opf-render": "^0.31.0"
190
190
  },
191
191
  "peerDependenciesMeta": {
192
192
  "@openpresentation/opf-pptx": {
@@ -207,5 +207,5 @@
207
207
  "test:packed": "node test/packed-install-smoke.mjs",
208
208
  "pack:dry-run": "pnpm run build:deps && pnpm run build && npm pack --dry-run --cache /tmp/opf-npm-cache"
209
209
  },
210
- "gitHead": "d82530c652b01fd6da204913c70636bfdf898642"
210
+ "gitHead": "d68561fad7b2b319cd7cdb8d7e75fefdd1dffa4c"
211
211
  }
@@ -1,16 +0,0 @@
1
- import { OPFApiError } from './chunk-I6G32TB5.js';
2
- import { CATALOG_SCHEMA } from './chunk-M7YZU3DX.js';
3
- import { CATALOG_SCHEMA as CATALOG_SCHEMA$1, gallery } from '@openpresentation/gallery';
4
-
5
- function galleryMismatch(declared, reads = CATALOG_SCHEMA) {
6
- if (declared === reads) return void 0;
7
- return `@openpresentation/gallery targets catalog schema ${JSON.stringify(declared)}, and this @openpresentation/opf reads catalog schema ${reads}. Install the gallery release for schema ${reads} (the range in @openpresentation/opf's dependencies), or pass catalogs explicitly.`;
8
- }
9
- var mismatch = galleryMismatch(CATALOG_SCHEMA$1);
10
- var DEFAULTS = Object.freeze([gallery]);
11
- function defaultCatalogs() {
12
- if (mismatch) throw new OPFApiError(mismatch, "gallery-schema-mismatch", { details: { gallery: CATALOG_SCHEMA$1, core: CATALOG_SCHEMA } });
13
- return DEFAULTS;
14
- }
15
-
16
- export { defaultCatalogs, galleryMismatch };