@openpresentation/mcp 0.29.1 → 0.30.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.
package/dist/bin.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runStdio
4
- } from "./chunk-FITF3ZIX.js";
5
- import "./chunk-JGRWY42H.js";
4
+ } from "./chunk-TMSRQH3S.js";
5
+ import "./chunk-OICJBLMP.js";
6
6
  import "./chunk-7STOTAET.js";
7
7
 
8
8
  // src/bin.ts
9
9
  var arg = process.argv[2];
10
10
  if (arg === "--help" || arg === "-h") {
11
- process.stdout.write(`opf-mcp ${"0.29.1"}: the OPF MCP server on stdio.
11
+ process.stdout.write(`opf-mcp ${"0.30.0"}: the OPF MCP server on stdio.
12
12
 
13
13
  Usage:
14
14
  npx -y @openpresentation/mcp run the server (an MCP client starts it: command npx, args -y @openpresentation/mcp)
@@ -20,7 +20,7 @@ opf://examples/<slug>, opf://catalog/<kind>/<id>, opf://skills/<name>. Prompts:
20
20
  choose_layout. It keeps no state and reads no file. Claude Code: claude mcp add opf -- npx -y @openpresentation/mcp
21
21
  `);
22
22
  } else if (arg === "--version" || arg === "-v") {
23
- process.stdout.write(`${"0.29.1"}
23
+ process.stdout.write(`${"0.30.0"}
24
24
  `);
25
25
  } else if (arg !== void 0) {
26
26
  process.stderr.write(`opf-mcp takes no arguments (got ${arg}). Run opf-mcp --help.
@@ -0,0 +1,71 @@
1
+ import {
2
+ createOpfMcpServer,
3
+ define_OPF_MCP_SKILLS_default
4
+ } from "./chunk-7STOTAET.js";
5
+
6
+ // src/data.ts
7
+ import { docs } from "@openpresentation/opf/docs";
8
+ import { examples } from "@openpresentation/opf/examples";
9
+ var DOC_SLUGS = [
10
+ "format-card",
11
+ "how-opf-works",
12
+ "quickstart",
13
+ "llm-authoring",
14
+ "agent-skills",
15
+ "mcp",
16
+ "schema-reference",
17
+ "validate",
18
+ "finding-schema-reference",
19
+ "content-payloads",
20
+ "content-item-design-overrides",
21
+ "design-resolution",
22
+ "dynamic-composition",
23
+ "layout-templates",
24
+ "slide-sizes",
25
+ "slide-design-principles",
26
+ "rich-text",
27
+ "numbered-lists",
28
+ "table-text-colors",
29
+ "image-treatments",
30
+ "chart-options",
31
+ "chart-table-data",
32
+ "footnotes-citations-captions",
33
+ "templates-and-variables",
34
+ "data-import",
35
+ "catalog-schema-reference",
36
+ "default-catalog",
37
+ "examples",
38
+ "stats",
39
+ "markdown",
40
+ "yaml",
41
+ "conversions",
42
+ "patch-diff-merge-format",
43
+ "node",
44
+ "cli",
45
+ "live-editor",
46
+ "open-ecosystem"
47
+ ];
48
+ function defaultSources() {
49
+ const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
50
+ return {
51
+ docs: DOC_SLUGS.flatMap((slug) => {
52
+ const doc = bySlug.get(slug);
53
+ return doc ? [{ slug: doc.slug, title: doc.title, markdown: doc.markdown }] : [];
54
+ }),
55
+ examples: examples.map((example) => ({ slug: example.slug, category: example.category, gallery: example.gallery, file: example.file, deck: example.deck })),
56
+ skills: define_OPF_MCP_SKILLS_default,
57
+ whatsNew: "# What's new in OPF\n\nThe last 5 releases from the CHANGELOG of @openpresentation/opf (the format, the validators and the engine), newest first. The renderer, PPTX, editor, CLI and gallery packages have their own changelogs, with the same version numbers.\n\n## 0.30.0 (2026-11-02)\n\nNo package changes; released with the set.\n\n## 0.29.1 (2026-10-27)\n\nNo package changes; released with the set.\n\n## 0.29.0 (2026-10-27)\n\n- The editor draws its first preview faster than 0.26.10 again (0.28.0 took up to 2.3 times as long; the playground's sample deck now draws in 2.3 s and a 300-slide deck in 3.4 s, against 3.2 s and 6.4 s in 0.26.10 and 4.1 s and 6.4 s in 0.28.0). The browser fonts handle's `pending`, which the editor asks before every render, composed every slide of the deck to find its font families (with core's default measurement: a dozen `\\p{...}` regular expressions per character, run again for every fitting trial, and 0.28.0's content sizing tries more sizes), then composed and drew every slide again to find its faces, on every call. Now the families come from each slide's design without composing it, each slide's faces and families are kept by the slide's content so an edit analyzes only the slides it changed, each drawn string is scanned for scripts once, and the handle's new `analysisStats` counts that work. Core's `measureText` classifies each character once (identical widths), and script detection classifies a character with one precompiled pattern and works out a text's Han script once. Results, rendered output and the fonts loaded before drawing are unchanged. The playground takes `OPF_EDITOR_HOST.document` (its starting deck) and `OPF_EDITOR_HOST.onReady` from a host page, and `test/startup-perf-browser.mjs` holds the startup time and the work counts.\n- A block that a stacked column draws at the full height it needs is no longer reported as a too-small cell (`small-cell`). Since the content-sized rows of 0.28.0, a slide of short one-line notes could be split by `paginate` or rejected by `overflow: 'error'` although it fits; a block squeezed below its need in a crowded column is still reported. The fix shipped in 0.28.0 after its notes were written.\n\n## 0.28.0 (2026-10-26)\n\n- Agent guidance from the 2026-10-26 outside audit: a chart written in the Chart.js or ECharts shape (`categories` and `series`, or `labels` and `datasets`, and no `data`) is one `opf/schema` finding whose hint shows the author's own values as `data: { columns, rows }`, with a patch fix, instead of a missing-`data` error plus one additional-property error per key; the `Chart` and `ChartData` schema descriptions state the shape with a one-line example. Every schema carries a `$comment` that says `v1` is the pre-1.0 line and may change in any 0.x minor release, `opf schemas` lists the `@openpresentation/opf` version beside each schema id, `opf schema` names it in the whole schema's `$comment`, `opf --version` names the schema id, and `docs/schema-versioning.md` explains how to pin it.\n- WI-2 (spec and composition; no existing record changes): a layout record that is a column whose `regions` entries are all sized `auto` (headings included) now places the whole stack, title and subtitle included, by the record's own `anchor`: `middle` centers it and `bottom` sits it at the foot in the content box's leftover height, `top` (the default) is today's result. Until now `anchor` only moved body content inside its region and never the title, and a record had no way to center a title with its supporting text. A record with any numeric entry, a row record and a split's `anchor` behave as before, and `LayoutTemplate.stack` reports the resolved anchor. The Layout Studio's layout Anchor field says so when the layout qualifies. Custom records that combine an all-`auto` column with a non-top record `anchor` (the anchor had no visible effect on the title before) now move as a stack; none of the 129 example decks and none of the gallery's records did before this change.\n- WI-6 (output-changing only for decks that set it): `design.listBullet` takes `\"none\"` (deck, slide, theme and layout record), for lists that are a label and its explanation. Core composes no marker and no hanging indent (each `listEntries[]` marker is `{ none: true, text: '', indent: 0 }`, nested levels still step in by 1.1 em, and a list with `numbering` keeps its numbers); the SVG, PNG and PDF renderers draw no marker; the PowerPoint export writes `a:buNone` with no margin or indent and records the line roles, so a re-import reads the list back as one list; the editor's List bullets control offers \"None (no bullets)\" (`setDesignOption(editor, \"listBullet\", \"none\")`). No migration: existing decks and the values `character` and `image` are unchanged.\n- The `opf/unresolved-reference` hint names the nearest records that exist (\"Did you mean 'two-column'?\", else the three nearest layouts, themes and so on) when a catalog is registered. It stays a warning, because catalogs are open; `llms.txt`, the format card, the validate docs and the `opf-author` and `opf-inspect` skills now say a deck is done only when `opf validate deck.opf.json --fail-on warning` passes.\n- The release set is seven packages: `@openpresentation/mcp` joins core, the gallery, the CLI, the renderer, PPTX and the editor at the shared version. The release scripts and workflows (publish order, smoke, verify, notes), CI scope and jobs, the changelog fragment vocabulary (`mcp`) and the checks that listed six packages know it. Its first publish needs the owner step O1b of `docs/programs/monorepo/owner-steps.md` (npm cannot hold a trusted publisher for a name that does not exist yet).\n- WI-1 (output-changing: content sizing): content takes the size and height it asks for before anything shrinks, and peers share one size.\n - An `auto` row of a layout takes what its content needs, up to the space the headings and the numeric rows' content leave, instead of at most half the content box. When the auto rows do not all fit, each gives up the same fraction of what it can give up above its content's height at the minimum font size, so three detailed passages in an all-auto stack keep 25 px (were 22/20/18 px), a code payload no longer overflows at 16 px, and a one-line note keeps nearly its need beside a dense row.\n - Content-sized text is measured the way the fit that places it measures it (outlines and raster padding included), so a single-line auto note draws at its 25 px starting size instead of 24 px: the closing next step, the comparison verdict and the notes of `metrics`, `chart`, `table` and `timeline`.\n - A metric value asks for its 76 px role size whatever its box and fits down only when it does not fit; it asked for `min(76, 0.28 \xD7 box height)`, which drew 46.27 px in a compact region and about 50 px on `dashboard`.\n - A region whose blocks flow in a `column`, and a group's blocks in one column, stack at the heights they need, one gap apart, placed by the region's `anchor`, instead of an equal share each (a one-line paragraph sat 248 px above its long sibling). Charts, pictures, tables, timelines and videos take what the measured blocks leave; a column with explicit `weights` keeps its tracks.\n - Peers side by side share one fitted size per kind (prose, lists): regions of one role in the same rows (`two-column`, `comparison`, three columns) and the blocks of one row of a `grid` or `row` flow. Three peer lists draw at one size (were 32/30/27 px).\n - The gallery's `dashboard`, `faq` and `statement` layout previews are regenerated with the new engine (metric values at 76 px, FAQ pairs stacked one gap apart); no gallery record changes.\n- WI-2 (output-changing for decks whose title and content alignment differ): the subtitle follows `titleAlignment` whenever it sits in the title area, as the tag already did, so one alignment axis holds the heading group on every slide (the Metrics layout over centered numbers now keeps a left subtitle under its left title). A layout with its own `subtitle` region and a slide's own `design.contentAlignment` keep today's content alignment, and covers are unchanged. `item.alignment` of a title-area subtitle is the resolved `titleAlignment`; the schema text, `docs/dynamic-composition.md` and `docs/design-resolution.md` say so.\n- WI-3 (output-changing: media frames):\n - A picture shown whole (`fit: \"contain\"`) takes a frame of its own proportions, centered across and placed down its cell by the region's `anchor` (top by default), instead of the whole cell with the picture centered in it. A landscape picture beside text now starts on the text's first line (it sat 30 px lower). `item.image.box` is the picture's frame, so a border, a shape or an overlay follows the picture; a frame set with `aspectRatio` or a circle shape stays centered, and `cover` and `stretch` are unchanged. A caption closes up under a top-anchored frame.\n - A video draws in a 16:9 frame, the largest that fits its cell, placed by the region's `anchor` like a contained picture, instead of filling a portrait cell (three columns drew 372 x 515 px frames around a play button). `item.box` is the frame; the renderer and the PPTX export draw the placeholder in it.\n - Automatic composition (a slide with no layout): a picture that sets no fit is shown whole (`contain`, was `cover`, which cropped a 16:9 picture into a tall strip); a chart is at most 4:3; a single row where a portrait picture sits beside other blocks gives the width the picture does not use to them; and two prose blocks whose measured demand differs by 2.5 times or more split the row 55:45 to 70:30 in favor of the longer one when that shortens it and adds the shorter at most one line. Automatic grid selection scores a whole picture against its own proportions. Explicit fits, a layout's region, placed (edge) pictures, explicit weights and the `gallery` layout keep their results.\n - `fitImage` reports a crop of exactly 0 instead of a rounding error such as -8e-17.\n - `intrinsicImageAspect` and `intrinsicImageSize` read a JPEG's EXIF orientation: orientations 5 to 8 (a phone photo held upright) report the proportions as shown, width and height swapped, so a contained picture's frame matches the picture.\n- WI-4 (output-changing: type roles): one readable type scale, headings without stranded words, typographic quotes and code in fitted panels.\n - Body prose and the subtitle ask for 28 px (21 pt), up from 25 px; list items stay at 32 px and a table keeps 25 px. `fitText` and `fitRichText` default to 28 px, the `flow: auto` long-block test counts lines at 28 px, and every size still fits down to `composition.minFontSize`. Explicit run sizes win. Paragraphs and lists of one stacked column that do not fit at their role sizes shrink together to one size (they read 23, 21 and 25 px before).\n - A wrapped title or subtitle is balanced: it keeps its size and line count and picks the break set with the least variance of line widths, with a credit for a line that ends a sentence (\"A smaller pilot. / A clearer decision.\", not \"A smaller pilot. A / clearer decision.\"). An explicit line break stays and body text keeps the greedy wrap. New `balance` option of `fitText` and `RichTextOptions`.\n - A quote body asks for 40 px and fits down through the former 28 px. Its marks are the typographic pair of the deck language (a new `QUOTE_MARKS` table and `quoteMarksFor`: \u201C\u2026\u201D for English, \u201E\u2026\u201C for German, \xAB\u2026\xBB for French and Spanish, \u300C\u2026\u300D for Japanese, ASCII only for a language the table does not list; a deck with no language reads as English). Attribution, role and source join with an en dash (` \u2013 `). The PPTX export writes the same characters (they come from the shared quote layout) and import reads them back, and still reads the older straight marks and ` - `. New `language` option of `layoutQuote`.\n - Code asks for 22 px (was 18 px) and fits down to the floor; the language or file label keeps its own 14 px role. `layoutCode` returns `bounds` and a code panel shorter than its cell is only as tall as its metadata, source lines and insets, top-anchored.\n - In auto mode a prose block that wraps takes a reading measure of about 70 average characters of its size and follows the content alignment, never at the cost of size; a lone block of up to two lines in a `flow: auto` region (and in an auto-mode composition) uses a 32 px lead size. A kept-empty `flow: auto` region reserves two lines of that size.\n - The gallery layout previews are regenerated with the new engine; no gallery record changes.\nWI-4 of the slide-design plan (`artifacts/slide-design/WORK-ITEMS.md`; triage rows P15, P16, P17, P21 and P34, owner decision D2). Output-changing for every deck with prose, a multi-line heading, a quote or code: render goldens, the 0.19 template goldens and the PPTX archive fixtures move. Validation findings follow the new sizes; a dense deck that fitted at 25 px can fit at a smaller shared size.\n- WI-1 (P24): `validate` and `paginate` compose with the role families the renderer draws (new `resolveDrawnFontFamilies`, which `toSvg` now uses too), so a weight the open replacement lacks draws its nearest weight, recorded as a visual substitution, instead of failing the slide. With `loadFonts({ pack: 'office' })` every metric, quote and timeline slide of the default Aptos scheme reported `opf/layout-failed` and skipped its geometry checks (54 of the 291 slide-design audit renders) while `toSvg` drew it; a family no font resolves still fails. `opf validate` now measures with the fonts `opf convert` draws with when `@openpresentation/opf-render` is installed (`checks.layout: \"measured\"`), as `opf paginate` does; without it the report says the layout was estimated and how to install the renderer.\n- WI-2: `opf/reading-order` no longer reports content that sits in a column beside the title as \"drawn above the title\" when it is taller than the title and so starts above it (a title and a list side by side, as in the `agenda-split` layout). Only content drawn entirely above the title, over the title's own columns, is reported, as before. An area that spans only `auto` rows (because an empty region collapsed into it, as the all-`auto` stack layouts do) is no longer sized to zero height.\n\n## 0.27.0 (2026-10-26)\n\n- CE-01: `validate` runs with no runtime code generation on edge runtimes. The package ships validators compiled at build time (Ajv standalone code, the same schemas, options and formats), and the `workerd`, `worker` and `edge-light` export conditions load them through an internal `#schema-engine` import, so Cloudflare Workers and Vercel Edge need no alias and no build step; Node, Bun, Deno and the browser keep Ajv. They give the same verdicts and findings, which `test/precompiled-validators.test.mjs` checks against Ajv on every example deck, on catalog records and on random mutants of both, and on every `oneOf` and `anyOf` branch. `import \"@openpresentation/opf/precompiled\"` installs them where the conditions did not (code bundled for Node that runs on a worker, a page under a strict Content-Security-Policy), at about 78 KB gzipped more than the Ajv build. A new CI lane runs the package in the real `workerd` runtime and requires the example decks' findings to equal Node's. See docs/node.md, \"Edge runtimes\".\n- CE-04, CE-05: ids, id-addressed paths, `documentHash`, `expectHash` and patch scope, new `@openpresentation/opf/ids` entry (all also on the package root). `assignIds(document, { generate? })` fills the missing `id` of slides and content payloads deterministically (`slide-1`, `block-1`, ... skipping ids in use) and never changes an existing one; it returns `{ document, patch, assigned }` and throws `OPFIdError` for a `generate` that returns an unusable id. In an array, the pointer token `@intro` names the element whose `id` is `intro` (`/slides/@kpis/blocks/@note/text`): `applyPatch`, `applyPatchWithInverse`, `readPointer`, `getAtPointer` and `hasPointer` resolve it (an object key that starts with `@` is still a plain key), `resolveIdPointer` and `toIdPointer` convert a pointer, `resolveIdPatch` and `toIdPatch` a whole patch operation by operation, and the new error codes are `patch-id-not-found` and `patch-id-ambiguous`; inverse patches stay in index form. `documentHash(document)` is `sha256:` and the SHA-256 of the document's key-sorted canonical JSON (RFC 8785), in pure synchronous JavaScript that is the same in Node, browsers and edge runtimes; `applyPatch`, `applyPatchWithInverse` and `edit` take `expectHash` and throw `OPFPatchHashError` (`patch-hash-mismatch`, with `expected` and `actual`) before applying anything when the document changed, or `invalid-expect-hash` for a value that is not a hash. `touches(patch, document?)` returns what a patch reads and writes (`reads`, `writes`, each with `pointer`, `idPointer`, `kind`, `ids` and `slideIds`, plus the union and the kinds) for conflict detection and permission checks. `walkPatch`, the one loop under patch application, is exported from `/patch` for these callers.\n- WI-5 (P18, owner decision D3, output-changing on dark backgrounds): a metric value and series 1 of a chart, drawn from the scheme on a dark surface, reach 4.5:1 contrast (the default blue `#2874A6` on the default navy `#011842` was 3.42:1; it is now lifted in lightness, keeping its hue, with the existing `legible` helper). The palette, light surfaces and explicit colors (`chart.colors`, a host palette) are unchanged. New on `/composition`: `metricValueColor`, `METRIC_VALUE_MIN_CONTRAST`, `chartLeadFromScheme`, `CHART_LEAD_MIN_CONTRAST` and the `legibleLead` option of `chartPaletteForFill`; the preview and the PPTX export call them with the same inputs, and the colorblind-safe palette rule reads the lifted palette.\n- WI-5 (P19, output-changing for every column, bar, line and area chart): the value axis follows its length. Intervals are at least 48 reference pixels apart and there are at most eight, so a chart beside text or in one of three columns draws 0 to 100% in steps of 20 and no longer 0 to 90% in tens (`chartIntervalsFor`, `CHART_MIN_TICK_SPACING_PX`, `CHART_MAX_INTERVALS` on `/composition`; the PPTX export writes the same `c:majorUnit`). A chart box with room for it (at least 216 x 288 reference pixels) floors its text at 18 px instead of 16 (`chartTextSize`, `CHART_TEXT_FLOOR_PX`), so three-column and dashboard charts read larger in the preview and in PowerPoint. The preview draws the chart panel without an outer frame (the axes and gridlines draw the plot, and the PPTX chart area never had a line). Zero baselines, explicit axis settings and the other chart types' own scales are unchanged. The tick-label column of the waterfall, histogram and pareto previews is sized for the sparser axis, so \"100\" no longer wraps after \"90\". The gallery's layout previews for `chart`, `chart-beside`, `table`, `table-beside` and `dashboard` are regenerated to match.\n- WI-5 (P20 and N2, output-changing for every table): column widths come from content and a line breaks only between words. Each column is at least as wide as its longest unbreakable word at the table's type size, short columns keep the width their text asks for and the rest is shared evenly (`TableLayout.columnWidths`); when even the longest words do not fit, the whole table shrinks within the readability floor before any word is split (`TableLayout.splitsWords` is true only at the floor). \"Completi/on\", \"Escalatio/n rate\" and \"Participa/nt\" in narrow peer-column tables no longer break. A table draws horizontal rules only by default, a rule under every row and over the first, with no vertical grid; an explicit cell border (including left and right) is still drawn. The PPTX export writes the same column widths (`a:gridCol`) and the same horizontal-only borders. The `TableCellStyle.borders` description says so.\n- WI-5 (P22, output-changing for timelines in narrow cells): when an event has less than 8 ems of its label size (176 px at the 22 px label size) of width, the timeline is composed vertically, with the events stacked down the cell and the same markers, statuses and order, instead of wrapping one word per line around a horizontal connector (two- and three-column timelines, a timeline beside a list or a metric). Full-width timelines keep their alternating arrangement and sizes; a cell too short for the vertical stack still falls back to the previous search.\n"
58
+ };
59
+ }
60
+
61
+ // src/index.ts
62
+ function createOpfServer(options) {
63
+ return createOpfMcpServer({ ...options, opfVersion: options.opfVersion ?? "0.30.0", sources: { ...defaultSources(), ...options.sources } });
64
+ }
65
+
66
+ export {
67
+ DOC_SLUGS,
68
+ defaultSources,
69
+ createOpfServer
70
+ };
71
+ //# sourceMappingURL=chunk-OICJBLMP.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createOpfServer
3
- } from "./chunk-JGRWY42H.js";
3
+ } from "./chunk-OICJBLMP.js";
4
4
 
5
5
  // src/stdio.ts
6
6
  import { createRequire } from "module";
@@ -21,7 +21,7 @@ async function checkDeckText(raw, format, options) {
21
21
  }
22
22
  async function runStdio(host = {}) {
23
23
  const opfVersion = host.opfVersion ?? createRequire(import.meta.url)("@openpresentation/opf/package.json").version;
24
- const server = createOpfServer({ version: host.version ?? "0.29.1", opfVersion, checkText: checkDeckText });
24
+ const server = createOpfServer({ version: host.version ?? "0.30.0", opfVersion, checkText: checkDeckText });
25
25
  process.stdin.once("close", () => void server.close());
26
26
  await server.connect(new StdioServerTransport());
27
27
  }
@@ -30,4 +30,4 @@ export {
30
30
  checkDeckText,
31
31
  runStdio
32
32
  };
33
- //# sourceMappingURL=chunk-FITF3ZIX.js.map
33
+ //# sourceMappingURL=chunk-TMSRQH3S.js.map
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  DOC_SLUGS,
3
3
  createOpfServer,
4
4
  defaultSources
5
- } from "./chunk-JGRWY42H.js";
5
+ } from "./chunk-OICJBLMP.js";
6
6
  import {
7
7
  McpToolError,
8
8
  createOpfMcpServer,
package/dist/stdio.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  checkDeckText,
3
3
  runStdio
4
- } from "./chunk-FITF3ZIX.js";
5
- import "./chunk-JGRWY42H.js";
4
+ } from "./chunk-TMSRQH3S.js";
5
+ import "./chunk-OICJBLMP.js";
6
6
  import "./chunk-7STOTAET.js";
7
7
  export {
8
8
  checkDeckText,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/mcp",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
4
4
  "description": "An MCP server for OPF: learn the format (docs, schemas, the default catalog, examples and agent skills as resources and tools), validate and convert decks. Runs locally on stdio (opf-mcp) or hosted on an edge runtime.",
5
5
  "license": "MIT",
6
6
  "author": "Open Presentation",
@@ -37,19 +37,19 @@
37
37
  "dependencies": {
38
38
  "@modelcontextprotocol/sdk": "1.32.2",
39
39
  "zod": "4.6.7",
40
- "@openpresentation/gallery": "^0.29.1",
41
- "@openpresentation/opf": "^0.29.1"
40
+ "@openpresentation/gallery": "^0.30.0",
41
+ "@openpresentation/opf": "^0.30.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^24.13.4",
45
45
  "tsup": "^9.0.0",
46
46
  "typescript": "~5.9.3",
47
47
  "workerd": "1.20261023.1",
48
- "@openpresentation/opf-render": "0.29.1",
49
- "@openpresentation/cli": "0.29.1"
48
+ "@openpresentation/cli": "0.30.0",
49
+ "@openpresentation/opf-render": "0.30.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@openpresentation/opf-render": "^0.29.1"
52
+ "@openpresentation/opf-render": "^0.30.0"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@openpresentation/opf-render": {
@@ -82,5 +82,5 @@
82
82
  "clean": "rm -rf dist",
83
83
  "test": "pnpm run build:deps && pnpm run build && node ../../scripts/run-tests.mjs"
84
84
  },
85
- "gitHead": "e9f46c24606ce0f474347b984eacea5dce7fd839"
85
+ "gitHead": "d82530c652b01fd6da204913c70636bfdf898642"
86
86
  }
@@ -1,71 +0,0 @@
1
- import {
2
- createOpfMcpServer,
3
- define_OPF_MCP_SKILLS_default
4
- } from "./chunk-7STOTAET.js";
5
-
6
- // src/data.ts
7
- import { docs } from "@openpresentation/opf/docs";
8
- import { examples } from "@openpresentation/opf/examples";
9
- var DOC_SLUGS = [
10
- "format-card",
11
- "how-opf-works",
12
- "quickstart",
13
- "llm-authoring",
14
- "agent-skills",
15
- "mcp",
16
- "schema-reference",
17
- "validate",
18
- "finding-schema-reference",
19
- "content-payloads",
20
- "content-item-design-overrides",
21
- "design-resolution",
22
- "dynamic-composition",
23
- "layout-templates",
24
- "slide-sizes",
25
- "slide-design-principles",
26
- "rich-text",
27
- "numbered-lists",
28
- "table-text-colors",
29
- "image-treatments",
30
- "chart-options",
31
- "chart-table-data",
32
- "footnotes-citations-captions",
33
- "templates-and-variables",
34
- "data-import",
35
- "catalog-schema-reference",
36
- "default-catalog",
37
- "examples",
38
- "stats",
39
- "markdown",
40
- "yaml",
41
- "conversions",
42
- "patch-diff-merge-format",
43
- "node",
44
- "cli",
45
- "live-editor",
46
- "open-ecosystem"
47
- ];
48
- function defaultSources() {
49
- const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
50
- return {
51
- docs: DOC_SLUGS.flatMap((slug) => {
52
- const doc = bySlug.get(slug);
53
- return doc ? [{ slug: doc.slug, title: doc.title, markdown: doc.markdown }] : [];
54
- }),
55
- examples: examples.map((example) => ({ slug: example.slug, category: example.category, gallery: example.gallery, file: example.file, deck: example.deck })),
56
- skills: define_OPF_MCP_SKILLS_default,
57
- whatsNew: "# What's new in OPF\n\nThe last 5 releases from the CHANGELOG of @openpresentation/opf (the format, the validators and the engine), newest first. The renderer, PPTX, editor, CLI and gallery packages have their own changelogs, with the same version numbers.\n\n## 0.29.1 (2026-10-27)\n\nNo package changes; released with the set.\n\n## 0.29.0 (2026-10-27)\n\n- The editor draws its first preview faster than 0.26.10 again (0.28.0 took up to 2.3 times as long; the playground's sample deck now draws in 2.3 s and a 300-slide deck in 3.4 s, against 3.2 s and 6.4 s in 0.26.10 and 4.1 s and 6.4 s in 0.28.0). The browser fonts handle's `pending`, which the editor asks before every render, composed every slide of the deck to find its font families (with core's default measurement: a dozen `\\p{...}` regular expressions per character, run again for every fitting trial, and 0.28.0's content sizing tries more sizes), then composed and drew every slide again to find its faces, on every call. Now the families come from each slide's design without composing it, each slide's faces and families are kept by the slide's content so an edit analyzes only the slides it changed, each drawn string is scanned for scripts once, and the handle's new `analysisStats` counts that work. Core's `measureText` classifies each character once (identical widths), and script detection classifies a character with one precompiled pattern and works out a text's Han script once. Results, rendered output and the fonts loaded before drawing are unchanged. The playground takes `OPF_EDITOR_HOST.document` (its starting deck) and `OPF_EDITOR_HOST.onReady` from a host page, and `test/startup-perf-browser.mjs` holds the startup time and the work counts.\n- A block that a stacked column draws at the full height it needs is no longer reported as a too-small cell (`small-cell`). Since the content-sized rows of 0.28.0, a slide of short one-line notes could be split by `paginate` or rejected by `overflow: 'error'` although it fits; a block squeezed below its need in a crowded column is still reported. The fix shipped in 0.28.0 after its notes were written.\n\n## 0.28.0 (2026-10-26)\n\n- Agent guidance from the 2026-10-26 outside audit: a chart written in the Chart.js or ECharts shape (`categories` and `series`, or `labels` and `datasets`, and no `data`) is one `opf/schema` finding whose hint shows the author's own values as `data: { columns, rows }`, with a patch fix, instead of a missing-`data` error plus one additional-property error per key; the `Chart` and `ChartData` schema descriptions state the shape with a one-line example. Every schema carries a `$comment` that says `v1` is the pre-1.0 line and may change in any 0.x minor release, `opf schemas` lists the `@openpresentation/opf` version beside each schema id, `opf schema` names it in the whole schema's `$comment`, `opf --version` names the schema id, and `docs/schema-versioning.md` explains how to pin it.\n- WI-2 (spec and composition; no existing record changes): a layout record that is a column whose `regions` entries are all sized `auto` (headings included) now places the whole stack, title and subtitle included, by the record's own `anchor`: `middle` centers it and `bottom` sits it at the foot in the content box's leftover height, `top` (the default) is today's result. Until now `anchor` only moved body content inside its region and never the title, and a record had no way to center a title with its supporting text. A record with any numeric entry, a row record and a split's `anchor` behave as before, and `LayoutTemplate.stack` reports the resolved anchor. The Layout Studio's layout Anchor field says so when the layout qualifies. Custom records that combine an all-`auto` column with a non-top record `anchor` (the anchor had no visible effect on the title before) now move as a stack; none of the 129 example decks and none of the gallery's records did before this change.\n- WI-6 (output-changing only for decks that set it): `design.listBullet` takes `\"none\"` (deck, slide, theme and layout record), for lists that are a label and its explanation. Core composes no marker and no hanging indent (each `listEntries[]` marker is `{ none: true, text: '', indent: 0 }`, nested levels still step in by 1.1 em, and a list with `numbering` keeps its numbers); the SVG, PNG and PDF renderers draw no marker; the PowerPoint export writes `a:buNone` with no margin or indent and records the line roles, so a re-import reads the list back as one list; the editor's List bullets control offers \"None (no bullets)\" (`setDesignOption(editor, \"listBullet\", \"none\")`). No migration: existing decks and the values `character` and `image` are unchanged.\n- The `opf/unresolved-reference` hint names the nearest records that exist (\"Did you mean 'two-column'?\", else the three nearest layouts, themes and so on) when a catalog is registered. It stays a warning, because catalogs are open; `llms.txt`, the format card, the validate docs and the `opf-author` and `opf-inspect` skills now say a deck is done only when `opf validate deck.opf.json --fail-on warning` passes.\n- The release set is seven packages: `@openpresentation/mcp` joins core, the gallery, the CLI, the renderer, PPTX and the editor at the shared version. The release scripts and workflows (publish order, smoke, verify, notes), CI scope and jobs, the changelog fragment vocabulary (`mcp`) and the checks that listed six packages know it. Its first publish needs the owner step O1b of `docs/programs/monorepo/owner-steps.md` (npm cannot hold a trusted publisher for a name that does not exist yet).\n- WI-1 (output-changing: content sizing): content takes the size and height it asks for before anything shrinks, and peers share one size.\n - An `auto` row of a layout takes what its content needs, up to the space the headings and the numeric rows' content leave, instead of at most half the content box. When the auto rows do not all fit, each gives up the same fraction of what it can give up above its content's height at the minimum font size, so three detailed passages in an all-auto stack keep 25 px (were 22/20/18 px), a code payload no longer overflows at 16 px, and a one-line note keeps nearly its need beside a dense row.\n - Content-sized text is measured the way the fit that places it measures it (outlines and raster padding included), so a single-line auto note draws at its 25 px starting size instead of 24 px: the closing next step, the comparison verdict and the notes of `metrics`, `chart`, `table` and `timeline`.\n - A metric value asks for its 76 px role size whatever its box and fits down only when it does not fit; it asked for `min(76, 0.28 \xD7 box height)`, which drew 46.27 px in a compact region and about 50 px on `dashboard`.\n - A region whose blocks flow in a `column`, and a group's blocks in one column, stack at the heights they need, one gap apart, placed by the region's `anchor`, instead of an equal share each (a one-line paragraph sat 248 px above its long sibling). Charts, pictures, tables, timelines and videos take what the measured blocks leave; a column with explicit `weights` keeps its tracks.\n - Peers side by side share one fitted size per kind (prose, lists): regions of one role in the same rows (`two-column`, `comparison`, three columns) and the blocks of one row of a `grid` or `row` flow. Three peer lists draw at one size (were 32/30/27 px).\n - The gallery's `dashboard`, `faq` and `statement` layout previews are regenerated with the new engine (metric values at 76 px, FAQ pairs stacked one gap apart); no gallery record changes.\n- WI-2 (output-changing for decks whose title and content alignment differ): the subtitle follows `titleAlignment` whenever it sits in the title area, as the tag already did, so one alignment axis holds the heading group on every slide (the Metrics layout over centered numbers now keeps a left subtitle under its left title). A layout with its own `subtitle` region and a slide's own `design.contentAlignment` keep today's content alignment, and covers are unchanged. `item.alignment` of a title-area subtitle is the resolved `titleAlignment`; the schema text, `docs/dynamic-composition.md` and `docs/design-resolution.md` say so.\n- WI-3 (output-changing: media frames):\n - A picture shown whole (`fit: \"contain\"`) takes a frame of its own proportions, centered across and placed down its cell by the region's `anchor` (top by default), instead of the whole cell with the picture centered in it. A landscape picture beside text now starts on the text's first line (it sat 30 px lower). `item.image.box` is the picture's frame, so a border, a shape or an overlay follows the picture; a frame set with `aspectRatio` or a circle shape stays centered, and `cover` and `stretch` are unchanged. A caption closes up under a top-anchored frame.\n - A video draws in a 16:9 frame, the largest that fits its cell, placed by the region's `anchor` like a contained picture, instead of filling a portrait cell (three columns drew 372 x 515 px frames around a play button). `item.box` is the frame; the renderer and the PPTX export draw the placeholder in it.\n - Automatic composition (a slide with no layout): a picture that sets no fit is shown whole (`contain`, was `cover`, which cropped a 16:9 picture into a tall strip); a chart is at most 4:3; a single row where a portrait picture sits beside other blocks gives the width the picture does not use to them; and two prose blocks whose measured demand differs by 2.5 times or more split the row 55:45 to 70:30 in favor of the longer one when that shortens it and adds the shorter at most one line. Automatic grid selection scores a whole picture against its own proportions. Explicit fits, a layout's region, placed (edge) pictures, explicit weights and the `gallery` layout keep their results.\n - `fitImage` reports a crop of exactly 0 instead of a rounding error such as -8e-17.\n - `intrinsicImageAspect` and `intrinsicImageSize` read a JPEG's EXIF orientation: orientations 5 to 8 (a phone photo held upright) report the proportions as shown, width and height swapped, so a contained picture's frame matches the picture.\n- WI-4 (output-changing: type roles): one readable type scale, headings without stranded words, typographic quotes and code in fitted panels.\n - Body prose and the subtitle ask for 28 px (21 pt), up from 25 px; list items stay at 32 px and a table keeps 25 px. `fitText` and `fitRichText` default to 28 px, the `flow: auto` long-block test counts lines at 28 px, and every size still fits down to `composition.minFontSize`. Explicit run sizes win. Paragraphs and lists of one stacked column that do not fit at their role sizes shrink together to one size (they read 23, 21 and 25 px before).\n - A wrapped title or subtitle is balanced: it keeps its size and line count and picks the break set with the least variance of line widths, with a credit for a line that ends a sentence (\"A smaller pilot. / A clearer decision.\", not \"A smaller pilot. A / clearer decision.\"). An explicit line break stays and body text keeps the greedy wrap. New `balance` option of `fitText` and `RichTextOptions`.\n - A quote body asks for 40 px and fits down through the former 28 px. Its marks are the typographic pair of the deck language (a new `QUOTE_MARKS` table and `quoteMarksFor`: \u201C\u2026\u201D for English, \u201E\u2026\u201C for German, \xAB\u2026\xBB for French and Spanish, \u300C\u2026\u300D for Japanese, ASCII only for a language the table does not list; a deck with no language reads as English). Attribution, role and source join with an en dash (` \u2013 `). The PPTX export writes the same characters (they come from the shared quote layout) and import reads them back, and still reads the older straight marks and ` - `. New `language` option of `layoutQuote`.\n - Code asks for 22 px (was 18 px) and fits down to the floor; the language or file label keeps its own 14 px role. `layoutCode` returns `bounds` and a code panel shorter than its cell is only as tall as its metadata, source lines and insets, top-anchored.\n - In auto mode a prose block that wraps takes a reading measure of about 70 average characters of its size and follows the content alignment, never at the cost of size; a lone block of up to two lines in a `flow: auto` region (and in an auto-mode composition) uses a 32 px lead size. A kept-empty `flow: auto` region reserves two lines of that size.\n - The gallery layout previews are regenerated with the new engine; no gallery record changes.\nWI-4 of the slide-design plan (`artifacts/slide-design/WORK-ITEMS.md`; triage rows P15, P16, P17, P21 and P34, owner decision D2). Output-changing for every deck with prose, a multi-line heading, a quote or code: render goldens, the 0.19 template goldens and the PPTX archive fixtures move. Validation findings follow the new sizes; a dense deck that fitted at 25 px can fit at a smaller shared size.\n- WI-1 (P24): `validate` and `paginate` compose with the role families the renderer draws (new `resolveDrawnFontFamilies`, which `toSvg` now uses too), so a weight the open replacement lacks draws its nearest weight, recorded as a visual substitution, instead of failing the slide. With `loadFonts({ pack: 'office' })` every metric, quote and timeline slide of the default Aptos scheme reported `opf/layout-failed` and skipped its geometry checks (54 of the 291 slide-design audit renders) while `toSvg` drew it; a family no font resolves still fails. `opf validate` now measures with the fonts `opf convert` draws with when `@openpresentation/opf-render` is installed (`checks.layout: \"measured\"`), as `opf paginate` does; without it the report says the layout was estimated and how to install the renderer.\n- WI-2: `opf/reading-order` no longer reports content that sits in a column beside the title as \"drawn above the title\" when it is taller than the title and so starts above it (a title and a list side by side, as in the `agenda-split` layout). Only content drawn entirely above the title, over the title's own columns, is reported, as before. An area that spans only `auto` rows (because an empty region collapsed into it, as the all-`auto` stack layouts do) is no longer sized to zero height.\n\n## 0.27.0 (2026-10-26)\n\n- CE-01: `validate` runs with no runtime code generation on edge runtimes. The package ships validators compiled at build time (Ajv standalone code, the same schemas, options and formats), and the `workerd`, `worker` and `edge-light` export conditions load them through an internal `#schema-engine` import, so Cloudflare Workers and Vercel Edge need no alias and no build step; Node, Bun, Deno and the browser keep Ajv. They give the same verdicts and findings, which `test/precompiled-validators.test.mjs` checks against Ajv on every example deck, on catalog records and on random mutants of both, and on every `oneOf` and `anyOf` branch. `import \"@openpresentation/opf/precompiled\"` installs them where the conditions did not (code bundled for Node that runs on a worker, a page under a strict Content-Security-Policy), at about 78 KB gzipped more than the Ajv build. A new CI lane runs the package in the real `workerd` runtime and requires the example decks' findings to equal Node's. See docs/node.md, \"Edge runtimes\".\n- CE-04, CE-05: ids, id-addressed paths, `documentHash`, `expectHash` and patch scope, new `@openpresentation/opf/ids` entry (all also on the package root). `assignIds(document, { generate? })` fills the missing `id` of slides and content payloads deterministically (`slide-1`, `block-1`, ... skipping ids in use) and never changes an existing one; it returns `{ document, patch, assigned }` and throws `OPFIdError` for a `generate` that returns an unusable id. In an array, the pointer token `@intro` names the element whose `id` is `intro` (`/slides/@kpis/blocks/@note/text`): `applyPatch`, `applyPatchWithInverse`, `readPointer`, `getAtPointer` and `hasPointer` resolve it (an object key that starts with `@` is still a plain key), `resolveIdPointer` and `toIdPointer` convert a pointer, `resolveIdPatch` and `toIdPatch` a whole patch operation by operation, and the new error codes are `patch-id-not-found` and `patch-id-ambiguous`; inverse patches stay in index form. `documentHash(document)` is `sha256:` and the SHA-256 of the document's key-sorted canonical JSON (RFC 8785), in pure synchronous JavaScript that is the same in Node, browsers and edge runtimes; `applyPatch`, `applyPatchWithInverse` and `edit` take `expectHash` and throw `OPFPatchHashError` (`patch-hash-mismatch`, with `expected` and `actual`) before applying anything when the document changed, or `invalid-expect-hash` for a value that is not a hash. `touches(patch, document?)` returns what a patch reads and writes (`reads`, `writes`, each with `pointer`, `idPointer`, `kind`, `ids` and `slideIds`, plus the union and the kinds) for conflict detection and permission checks. `walkPatch`, the one loop under patch application, is exported from `/patch` for these callers.\n- WI-5 (P18, owner decision D3, output-changing on dark backgrounds): a metric value and series 1 of a chart, drawn from the scheme on a dark surface, reach 4.5:1 contrast (the default blue `#2874A6` on the default navy `#011842` was 3.42:1; it is now lifted in lightness, keeping its hue, with the existing `legible` helper). The palette, light surfaces and explicit colors (`chart.colors`, a host palette) are unchanged. New on `/composition`: `metricValueColor`, `METRIC_VALUE_MIN_CONTRAST`, `chartLeadFromScheme`, `CHART_LEAD_MIN_CONTRAST` and the `legibleLead` option of `chartPaletteForFill`; the preview and the PPTX export call them with the same inputs, and the colorblind-safe palette rule reads the lifted palette.\n- WI-5 (P19, output-changing for every column, bar, line and area chart): the value axis follows its length. Intervals are at least 48 reference pixels apart and there are at most eight, so a chart beside text or in one of three columns draws 0 to 100% in steps of 20 and no longer 0 to 90% in tens (`chartIntervalsFor`, `CHART_MIN_TICK_SPACING_PX`, `CHART_MAX_INTERVALS` on `/composition`; the PPTX export writes the same `c:majorUnit`). A chart box with room for it (at least 216 x 288 reference pixels) floors its text at 18 px instead of 16 (`chartTextSize`, `CHART_TEXT_FLOOR_PX`), so three-column and dashboard charts read larger in the preview and in PowerPoint. The preview draws the chart panel without an outer frame (the axes and gridlines draw the plot, and the PPTX chart area never had a line). Zero baselines, explicit axis settings and the other chart types' own scales are unchanged. The tick-label column of the waterfall, histogram and pareto previews is sized for the sparser axis, so \"100\" no longer wraps after \"90\". The gallery's layout previews for `chart`, `chart-beside`, `table`, `table-beside` and `dashboard` are regenerated to match.\n- WI-5 (P20 and N2, output-changing for every table): column widths come from content and a line breaks only between words. Each column is at least as wide as its longest unbreakable word at the table's type size, short columns keep the width their text asks for and the rest is shared evenly (`TableLayout.columnWidths`); when even the longest words do not fit, the whole table shrinks within the readability floor before any word is split (`TableLayout.splitsWords` is true only at the floor). \"Completi/on\", \"Escalatio/n rate\" and \"Participa/nt\" in narrow peer-column tables no longer break. A table draws horizontal rules only by default, a rule under every row and over the first, with no vertical grid; an explicit cell border (including left and right) is still drawn. The PPTX export writes the same column widths (`a:gridCol`) and the same horizontal-only borders. The `TableCellStyle.borders` description says so.\n- WI-5 (P22, output-changing for timelines in narrow cells): when an event has less than 8 ems of its label size (176 px at the 22 px label size) of width, the timeline is composed vertically, with the events stacked down the cell and the same markers, statuses and order, instead of wrapping one word per line around a horizontal connector (two- and three-column timelines, a timeline beside a list or a metric). Full-width timelines keep their alternating arrangement and sizes; a cell too short for the vertical stack still falls back to the previous search.\n\n## 0.26.10 (2026-10-25)\n\nNo package changes; released with the set.\n"
58
- };
59
- }
60
-
61
- // src/index.ts
62
- function createOpfServer(options) {
63
- return createOpfMcpServer({ ...options, opfVersion: options.opfVersion ?? "0.29.1", sources: { ...defaultSources(), ...options.sources } });
64
- }
65
-
66
- export {
67
- DOC_SLUGS,
68
- defaultSources,
69
- createOpfServer
70
- };
71
- //# sourceMappingURL=chunk-JGRWY42H.js.map