@openpresentation/opf 0.10.1 → 0.11.1

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.
Files changed (280) hide show
  1. package/dist/{catalogs-DoVmvDr7.d.ts → catalogs-DJ-B5ZyD.d.ts} +79 -9
  2. package/dist/catalogs.d.ts +2 -2
  3. package/dist/catalogs.js +1 -1
  4. package/dist/{chunk-PQJRDNA6.js → chunk-6UPHKXI5.js} +381 -34
  5. package/dist/{chunk-TU7I3KSB.js → chunk-7LF37SG3.js} +7400 -5422
  6. package/dist/{chunk-EWJNRHXA.js → chunk-BP6WMSSS.js} +3 -3
  7. package/dist/{chunk-FHGJX5QK.js → chunk-KWIOQ3QR.js} +73 -32
  8. package/dist/{chunk-D3GQIREP.js → chunk-LTSDDBNW.js} +558 -52
  9. package/dist/{chunk-ZIL7MGZU.js → chunk-M4GTWRK7.js} +333 -102
  10. package/dist/chunk-NPYN4TZ5.js +3516 -0
  11. package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
  12. package/dist/composition-DzLwY-Mi.d.ts +887 -0
  13. package/dist/composition.d.ts +1 -676
  14. package/dist/composition.js +1 -1
  15. package/dist/docs.js +80 -20
  16. package/dist/examples.d.ts +1 -1
  17. package/dist/examples.js +11 -11
  18. package/dist/font-policy.d.ts +123 -0
  19. package/dist/font-policy.js +1 -0
  20. package/dist/index.d.ts +184 -4
  21. package/dist/index.js +473 -7
  22. package/dist/lint.d.ts +3 -3
  23. package/dist/lint.js +5 -5
  24. package/dist/pagination.d.ts +6 -2
  25. package/dist/pagination.js +5 -5
  26. package/dist/{presentation-BMTdX6O1.d.ts → presentation-bceTClm8.d.ts} +297 -30
  27. package/dist/repo-readme.js +1 -1
  28. package/dist/{schemas-BYe5y8-i.d.ts → schemas-X_NniU4A.d.ts} +8 -0
  29. package/dist/schemas.d.ts +1 -1
  30. package/dist/schemas.js +1 -1
  31. package/dist/spec/catalogs/audiences/academic.json +24 -0
  32. package/dist/spec/catalogs/audiences/all-hands.json +3 -1
  33. package/dist/spec/catalogs/audiences/board.json +3 -1
  34. package/dist/spec/catalogs/audiences/customer.json +25 -0
  35. package/dist/spec/catalogs/audiences/executive.json +24 -0
  36. package/dist/spec/catalogs/audiences/general-public.json +25 -0
  37. package/dist/spec/catalogs/audiences/index.json +73 -1
  38. package/dist/spec/catalogs/audiences/internal-team.json +25 -0
  39. package/dist/spec/catalogs/audiences/investor.json +25 -0
  40. package/dist/spec/catalogs/audiences/marketing.json +25 -0
  41. package/dist/spec/catalogs/audiences/media.json +24 -0
  42. package/dist/spec/catalogs/audiences/partner.json +25 -0
  43. package/dist/spec/catalogs/audiences/regulatory.json +25 -0
  44. package/dist/spec/catalogs/audiences/sales.json +25 -0
  45. package/dist/spec/catalogs/audiences/technical.json +25 -0
  46. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-2x.json +5 -0
  47. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-3x.json +5 -0
  48. package/dist/spec/catalogs/chart-types/100pct-bullet-bar.json +5 -0
  49. package/dist/spec/catalogs/chart-types/100pct-bullet-column-2x.json +5 -0
  50. package/dist/spec/catalogs/chart-types/100pct-bullet-column-3x.json +5 -0
  51. package/dist/spec/catalogs/chart-types/100pct-bullet-column.json +5 -0
  52. package/dist/spec/catalogs/chart-types/100pct-progress-bar.json +10 -0
  53. package/dist/spec/catalogs/chart-types/100pct-stacked-area-2x.json +10 -0
  54. package/dist/spec/catalogs/chart-types/100pct-stacked-area-3x.json +6 -1
  55. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-2x.json +10 -0
  56. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-3x.json +6 -1
  57. package/dist/spec/catalogs/chart-types/100pct-stacked-column-2x.json +10 -0
  58. package/dist/spec/catalogs/chart-types/100pct-stacked-column-3x.json +6 -1
  59. package/dist/spec/catalogs/chart-types/area.json +5 -0
  60. package/dist/spec/catalogs/chart-types/australia.json +10 -0
  61. package/dist/spec/catalogs/chart-types/bar.json +5 -0
  62. package/dist/spec/catalogs/chart-types/box-and-whisker-2x.json +10 -0
  63. package/dist/spec/catalogs/chart-types/box-and-whisker-3x.json +10 -0
  64. package/dist/spec/catalogs/chart-types/box-and-whisker.json +5 -0
  65. package/dist/spec/catalogs/chart-types/bullet-bar-2x.json +5 -0
  66. package/dist/spec/catalogs/chart-types/bullet-bar-3x.json +5 -0
  67. package/dist/spec/catalogs/chart-types/bullet-bar.json +5 -0
  68. package/dist/spec/catalogs/chart-types/bullet-column-2x.json +5 -0
  69. package/dist/spec/catalogs/chart-types/bullet-column-3x.json +5 -0
  70. package/dist/spec/catalogs/chart-types/bullet-column.json +5 -0
  71. package/dist/spec/catalogs/chart-types/canada.json +10 -0
  72. package/dist/spec/catalogs/chart-types/clustered-bar-2x.json +10 -0
  73. package/dist/spec/catalogs/chart-types/clustered-column.json +10 -0
  74. package/dist/spec/catalogs/chart-types/column.json +5 -0
  75. package/dist/spec/catalogs/chart-types/dot-plot-2x.json +5 -0
  76. package/dist/spec/catalogs/chart-types/dot-plot-3x.json +5 -0
  77. package/dist/spec/catalogs/chart-types/dot-plot-4x.json +5 -0
  78. package/dist/spec/catalogs/chart-types/dot-plot-5x.json +5 -0
  79. package/dist/spec/catalogs/chart-types/dot-plot-6x.json +5 -0
  80. package/dist/spec/catalogs/chart-types/dot-plot.json +5 -0
  81. package/dist/spec/catalogs/chart-types/doughnut.json +5 -0
  82. package/dist/spec/catalogs/chart-types/dumbbell.json +5 -0
  83. package/dist/spec/catalogs/chart-types/filled-radar.json +5 -0
  84. package/dist/spec/catalogs/chart-types/funnel.json +5 -0
  85. package/dist/spec/catalogs/chart-types/histogram.json +5 -0
  86. package/dist/spec/catalogs/chart-types/index.json +333 -233
  87. package/dist/spec/catalogs/chart-types/line-2x.json +10 -0
  88. package/dist/spec/catalogs/chart-types/line-3x.json +10 -0
  89. package/dist/spec/catalogs/chart-types/line-with-high-low-and-markers.json +5 -0
  90. package/dist/spec/catalogs/chart-types/line-with-high-low.json +5 -0
  91. package/dist/spec/catalogs/chart-types/line-with-markers-2x.json +10 -0
  92. package/dist/spec/catalogs/chart-types/line-with-markers-3x.json +10 -0
  93. package/dist/spec/catalogs/chart-types/line-with-markers.json +5 -0
  94. package/dist/spec/catalogs/chart-types/line.json +5 -0
  95. package/dist/spec/catalogs/chart-types/pareto.json +9 -14
  96. package/dist/spec/catalogs/chart-types/pie.json +5 -0
  97. package/dist/spec/catalogs/chart-types/radar-with-markers.json +5 -0
  98. package/dist/spec/catalogs/chart-types/radar.json +5 -0
  99. package/dist/spec/catalogs/chart-types/scatter.json +5 -0
  100. package/dist/spec/catalogs/chart-types/sparkline-2x.json +5 -0
  101. package/dist/spec/catalogs/chart-types/sparkline-3x.json +5 -0
  102. package/dist/spec/catalogs/chart-types/sparkline-4x.json +5 -0
  103. package/dist/spec/catalogs/chart-types/sparkline-5x.json +5 -0
  104. package/dist/spec/catalogs/chart-types/sparkline-6x.json +5 -0
  105. package/dist/spec/catalogs/chart-types/sparkline.json +5 -0
  106. package/dist/spec/catalogs/chart-types/stacked-area-2x.json +10 -0
  107. package/dist/spec/catalogs/chart-types/stacked-area-3x.json +6 -1
  108. package/dist/spec/catalogs/chart-types/stacked-bar-2x.json +10 -0
  109. package/dist/spec/catalogs/chart-types/stacked-bar-3x.json +6 -1
  110. package/dist/spec/catalogs/chart-types/stacked-column-2x.json +10 -0
  111. package/dist/spec/catalogs/chart-types/stacked-column-3x.json +6 -1
  112. package/dist/spec/catalogs/chart-types/stacked-line-2x.json +10 -0
  113. package/dist/spec/catalogs/chart-types/stacked-line-3x.json +6 -1
  114. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-2x.json +10 -0
  115. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-3x.json +6 -1
  116. package/dist/spec/catalogs/chart-types/treemap-2x.json +10 -0
  117. package/dist/spec/catalogs/chart-types/treemap-3x.json +10 -0
  118. package/dist/spec/catalogs/chart-types/treemap.json +5 -0
  119. package/dist/spec/catalogs/chart-types/united-kingdom.json +10 -0
  120. package/dist/spec/catalogs/chart-types/united-states.json +10 -0
  121. package/dist/spec/catalogs/chart-types/waterfall.json +5 -0
  122. package/dist/spec/catalogs/chart-types/world.json +6 -1
  123. package/dist/spec/catalogs/font-schemes/consolas.json +3 -0
  124. package/dist/spec/catalogs/font-schemes/courier-new.json +3 -0
  125. package/dist/spec/catalogs/languages/afrikaans.json +3 -0
  126. package/dist/spec/catalogs/languages/albanian.json +3 -0
  127. package/dist/spec/catalogs/languages/amharic.json +3 -0
  128. package/dist/spec/catalogs/languages/arabic.json +3 -0
  129. package/dist/spec/catalogs/languages/armenian.json +3 -0
  130. package/dist/spec/catalogs/languages/aymara.json +3 -0
  131. package/dist/spec/catalogs/languages/azerbaijani.json +3 -0
  132. package/dist/spec/catalogs/languages/bengali.json +3 -0
  133. package/dist/spec/catalogs/languages/berber-latin.json +3 -0
  134. package/dist/spec/catalogs/languages/bosnian-latin.json +3 -0
  135. package/dist/spec/catalogs/languages/bulgarian.json +3 -0
  136. package/dist/spec/catalogs/languages/catalan.json +3 -0
  137. package/dist/spec/catalogs/languages/cebuano.json +3 -0
  138. package/dist/spec/catalogs/languages/chinese-simplified.json +3 -0
  139. package/dist/spec/catalogs/languages/chinese-traditional.json +3 -0
  140. package/dist/spec/catalogs/languages/chittagonian.json +3 -0
  141. package/dist/spec/catalogs/languages/croatian.json +3 -0
  142. package/dist/spec/catalogs/languages/czech.json +3 -0
  143. package/dist/spec/catalogs/languages/danish.json +3 -0
  144. package/dist/spec/catalogs/languages/dutch.json +3 -0
  145. package/dist/spec/catalogs/languages/english-au.json +3 -0
  146. package/dist/spec/catalogs/languages/english-ca.json +3 -0
  147. package/dist/spec/catalogs/languages/english-gb.json +3 -0
  148. package/dist/spec/catalogs/languages/english-in.json +3 -0
  149. package/dist/spec/catalogs/languages/english-us.json +3 -0
  150. package/dist/spec/catalogs/languages/english.json +3 -0
  151. package/dist/spec/catalogs/languages/estonian.json +3 -0
  152. package/dist/spec/catalogs/languages/filipino.json +3 -0
  153. package/dist/spec/catalogs/languages/finnish.json +3 -0
  154. package/dist/spec/catalogs/languages/french.json +3 -0
  155. package/dist/spec/catalogs/languages/fulfulde.json +3 -0
  156. package/dist/spec/catalogs/languages/galician.json +3 -0
  157. package/dist/spec/catalogs/languages/georgian.json +3 -0
  158. package/dist/spec/catalogs/languages/german.json +3 -0
  159. package/dist/spec/catalogs/languages/greek.json +3 -0
  160. package/dist/spec/catalogs/languages/gujarati.json +3 -0
  161. package/dist/spec/catalogs/languages/hausa.json +3 -0
  162. package/dist/spec/catalogs/languages/hebrew.json +3 -0
  163. package/dist/spec/catalogs/languages/hindi.json +3 -0
  164. package/dist/spec/catalogs/languages/hungarian.json +3 -0
  165. package/dist/spec/catalogs/languages/igbo.json +3 -0
  166. package/dist/spec/catalogs/languages/indonesian.json +3 -0
  167. package/dist/spec/catalogs/languages/italian.json +3 -0
  168. package/dist/spec/catalogs/languages/japanese.json +3 -0
  169. package/dist/spec/catalogs/languages/kannada.json +3 -0
  170. package/dist/spec/catalogs/languages/kazakh.json +3 -0
  171. package/dist/spec/catalogs/languages/khmer.json +3 -0
  172. package/dist/spec/catalogs/languages/kinyarwanda.json +3 -0
  173. package/dist/spec/catalogs/languages/korean.json +3 -0
  174. package/dist/spec/catalogs/languages/kurmanji.json +3 -0
  175. package/dist/spec/catalogs/languages/latvian.json +3 -0
  176. package/dist/spec/catalogs/languages/lithuanian.json +3 -0
  177. package/dist/spec/catalogs/languages/macedonian.json +3 -0
  178. package/dist/spec/catalogs/languages/malagasy.json +3 -0
  179. package/dist/spec/catalogs/languages/malay.json +3 -0
  180. package/dist/spec/catalogs/languages/malayalam.json +3 -0
  181. package/dist/spec/catalogs/languages/maori.json +3 -0
  182. package/dist/spec/catalogs/languages/marathi.json +3 -0
  183. package/dist/spec/catalogs/languages/mongolian.json +3 -0
  184. package/dist/spec/catalogs/languages/nepali.json +3 -0
  185. package/dist/spec/catalogs/languages/norwegian.json +3 -0
  186. package/dist/spec/catalogs/languages/odia.json +3 -0
  187. package/dist/spec/catalogs/languages/oromo.json +3 -0
  188. package/dist/spec/catalogs/languages/pashto.json +3 -0
  189. package/dist/spec/catalogs/languages/persian.json +3 -0
  190. package/dist/spec/catalogs/languages/polish.json +3 -0
  191. package/dist/spec/catalogs/languages/portuguese.json +3 -0
  192. package/dist/spec/catalogs/languages/punjabi-gurmukhi.json +3 -0
  193. package/dist/spec/catalogs/languages/punjabi-shahmukhi.json +3 -0
  194. package/dist/spec/catalogs/languages/romanian.json +3 -0
  195. package/dist/spec/catalogs/languages/russian.json +3 -0
  196. package/dist/spec/catalogs/languages/serbian-cyrillic.json +3 -0
  197. package/dist/spec/catalogs/languages/serbian-latin.json +3 -0
  198. package/dist/spec/catalogs/languages/shona.json +3 -0
  199. package/dist/spec/catalogs/languages/slovak.json +3 -0
  200. package/dist/spec/catalogs/languages/slovenian.json +3 -0
  201. package/dist/spec/catalogs/languages/somali.json +3 -0
  202. package/dist/spec/catalogs/languages/spanish.json +3 -0
  203. package/dist/spec/catalogs/languages/swahili.json +3 -0
  204. package/dist/spec/catalogs/languages/swedish.json +3 -0
  205. package/dist/spec/catalogs/languages/tagalog.json +3 -0
  206. package/dist/spec/catalogs/languages/tajik.json +3 -0
  207. package/dist/spec/catalogs/languages/tamil.json +3 -0
  208. package/dist/spec/catalogs/languages/telugu.json +3 -0
  209. package/dist/spec/catalogs/languages/thai.json +3 -0
  210. package/dist/spec/catalogs/languages/turkish.json +3 -0
  211. package/dist/spec/catalogs/languages/ukrainian.json +3 -0
  212. package/dist/spec/catalogs/languages/urdu.json +3 -0
  213. package/dist/spec/catalogs/languages/uzbek-latin.json +3 -0
  214. package/dist/spec/catalogs/languages/vietnamese-quoc-ngu.json +3 -0
  215. package/dist/spec/catalogs/languages/xhosa.json +3 -0
  216. package/dist/spec/catalogs/languages/yoruba.json +3 -0
  217. package/dist/spec/catalogs/languages/zulu.json +3 -0
  218. package/dist/spec/catalogs/narratives/board-meeting.json +21 -21
  219. package/dist/spec/catalogs/narratives/business-narrative.json +8 -8
  220. package/dist/spec/catalogs/narratives/business-review.json +12 -12
  221. package/dist/spec/catalogs/narratives/capacity-planning.json +8 -8
  222. package/dist/spec/catalogs/narratives/challenge-resolution.json +7 -7
  223. package/dist/spec/catalogs/narratives/change-story.json +64 -0
  224. package/dist/spec/catalogs/narratives/classic-story.json +7 -7
  225. package/dist/spec/catalogs/narratives/company-intro.json +9 -9
  226. package/dist/spec/catalogs/narratives/conference-talk.json +1 -1
  227. package/dist/spec/catalogs/narratives/data-story.json +66 -0
  228. package/dist/spec/catalogs/narratives/early-startup-pitch.json +12 -12
  229. package/dist/spec/catalogs/narratives/educate.json +9 -9
  230. package/dist/spec/catalogs/narratives/employee-review.json +8 -8
  231. package/dist/spec/catalogs/narratives/failure-analysis.json +10 -10
  232. package/dist/spec/catalogs/narratives/focus.json +12 -12
  233. package/dist/spec/catalogs/narratives/golden-circle.json +1 -1
  234. package/dist/spec/catalogs/narratives/heros-journey.json +64 -0
  235. package/dist/spec/catalogs/narratives/index.json +81 -0
  236. package/dist/spec/catalogs/narratives/innovation.json +9 -9
  237. package/dist/spec/catalogs/narratives/justice.json +11 -11
  238. package/dist/spec/catalogs/narratives/marketing-strategy.json +11 -11
  239. package/dist/spec/catalogs/narratives/performance-improvement-plan.json +8 -8
  240. package/dist/spec/catalogs/narratives/performance-review.json +11 -11
  241. package/dist/spec/catalogs/narratives/persuade.json +7 -7
  242. package/dist/spec/catalogs/narratives/persuasive-sales.json +8 -8
  243. package/dist/spec/catalogs/narratives/pitch-deck.json +3 -3
  244. package/dist/spec/catalogs/narratives/problem-solution.json +2 -2
  245. package/dist/spec/catalogs/narratives/product-launch.json +10 -10
  246. package/dist/spec/catalogs/narratives/project-proposal.json +10 -10
  247. package/dist/spec/catalogs/narratives/pyramid-principle.json +48 -0
  248. package/dist/spec/catalogs/narratives/qbr.json +3 -3
  249. package/dist/spec/catalogs/narratives/rags-to-riches.json +7 -7
  250. package/dist/spec/catalogs/narratives/reveal.json +8 -8
  251. package/dist/spec/catalogs/narratives/scqa.json +1 -1
  252. package/dist/spec/catalogs/narratives/situation-complication-resolution.json +56 -0
  253. package/dist/spec/catalogs/narratives/sparkline.json +56 -0
  254. package/dist/spec/catalogs/narratives/star-method.json +56 -0
  255. package/dist/spec/catalogs/narratives/status-update.json +6 -6
  256. package/dist/spec/catalogs/narratives/strategic-advisory.json +7 -7
  257. package/dist/spec/catalogs/narratives/strategic-narrative.json +1 -1
  258. package/dist/spec/catalogs/narratives/survey-analysis.json +10 -10
  259. package/dist/spec/catalogs/narratives/survival-story.json +10 -10
  260. package/dist/spec/catalogs/narratives/transformation-arc.json +1 -1
  261. package/dist/spec/catalogs/narratives/trend-analysis.json +7 -7
  262. package/dist/spec/catalogs/narratives/underdog-victory.json +8 -8
  263. package/dist/spec/catalogs/narratives/venture-pitch.json +12 -12
  264. package/dist/spec/catalogs/narratives/vision-roadmap.json +65 -0
  265. package/dist/spec/catalogs/narratives/weekly-progress.json +10 -10
  266. package/dist/spec/catalogs/narratives/what-so-what-now-what.json +48 -0
  267. package/dist/spec/openapi.yaml +5 -0
  268. package/dist/spec/reference/engine-defaults.json +2 -2
  269. package/dist/spec/reference/font-policy.json +3435 -0
  270. package/dist/spec/reference/font-policy.schema.json +328 -0
  271. package/dist/spec/schemas/chart-type.schema.json +51 -11
  272. package/dist/spec/schemas/font-scheme.schema.json +91 -1
  273. package/dist/spec/schemas/language.schema.json +10 -5
  274. package/dist/spec/schemas/opf.schema.json +241 -32
  275. package/dist/spec-files.d.ts +1 -1
  276. package/dist/spec-files.js +1 -1
  277. package/dist/types.d.ts +3 -3
  278. package/dist/validator.d.ts +7 -4
  279. package/dist/validator.js +4 -4
  280. package/package.json +6 -2
package/dist/docs.js CHANGED
@@ -4,49 +4,55 @@ var docsData = Object.freeze([
4
4
  "slug": "agent-skills",
5
5
  "file": "docs/agent-skills.md",
6
6
  "title": "AI agent skills for OPF",
7
- "markdown": "# AI agent skills for OPF\n\nThe repository ships six reusable skills in `skills/`. Each folder has a `SKILL.md` entrypoint and optional references, assets, or scripts. `agents/openai.yaml` supplies Codex display metadata; the instructions themselves are Markdown and do not require a hosted service.\n\n| Skill | Use it for |\n| --- | --- |\n| [opf-author](../skills/opf-author/SKILL.md) | Turn briefs and source material into valid OPF content; includes a complete starter deck |\n| [opf-layout](../skills/opf-layout/SKILL.md) | Dynamic composition, nested groups, promoted regions, overflow repair, and pagination |\n| [opf-presets](../skills/opf-presets/SKILL.md) | Catalog discovery, design inheritance, gallery reuse, colors, themes, and fonts |\n| [opf-edit](../skills/opf-edit/SKILL.md) | Precise JSON Patch edits, undo, canvas/schema integration, and copy/import |\n| [opf-export](../skills/opf-export/SKILL.md) | Browser previews, SVG/PNG/PDF/PPTX, assets, fonts, and export verification |\n| [opf-inspect](../skills/opf-inspect/SKILL.md) | Exact schema fields, catalog IDs, validation errors, and reference warnings |\n\nLoad only the skills relevant to the request. They distinguish the portable format from current renderer/editor capabilities, and distinguish imported document instructions from the user's request. They do not authorize publishing, sending decks, or changing unrelated project configuration.\n\n## Use from a checkout\n\nAn agent can read the entrypoint directly, for example:\n\n> Use `skills/opf-author/SKILL.md` to create a decision brief in OPF, then validate it using `skills/opf-inspect/SKILL.md`.\n\nThe root `AGENTS.md` points repository agents to these entrypoints. Skills read the current project's schema and package exports instead of hardcoding a historical field count or assuming a public package has unreleased APIs.\n\n## Install in an agent environment\n\nThe installer introduced in CLI 0.5.0 bundles all six complete skill folders. Use Node 24 for this checkout and the next release. From your project directory:\n\n```sh\nnpx @openpresentation/cli@latest skills install\n```\n\nThe default installs copies into `.agents/skills` in the current project, suitable for agents including Codex. It does not change AGENTS.md or any agent configuration. No symlink privileges, paid service, API key or AI provider is required. npm downloads the CLI on first use; the installed CLI then installs its bundled skills without network access. Pin `@openpresentation/cli@0.5.0` for a repeatable version. This command requires the 0.5.0 release; when testing its release branch before publication, use `node packages/cli/dist/index.js skills install` after building.\n\n| Target | Project directory | Personal directory with `--global` |\n| --- | --- | --- |\n| Default / `--agent universal` | `.agents/skills` | `~/.agents/skills` |\n| `--agent codex` | `.agents/skills` | `~/.codex/skills` |\n| `--agent claude-code` | `.claude/skills` | `~/.claude/skills` |\n| `--agent cursor` | `.cursor/skills` | `~/.cursor/skills` |\n\nFor example, `npx @openpresentation/cli@latest skills install --agent codex --global` installs personal Codex skills. For another compatible agent use `--directory <its-skills-directory>`; this option cannot be combined with `--agent` or `--global`. Restart or reload your agent if its skill discovery requires it. A compatible client can invoke the installed skills with names such as `$opf-author` or `$opf-inspect`.\n\nInspect or update the same destination:\n\n```sh\nnpx @openpresentation/cli@latest skills status\nnpx @openpresentation/cli@latest skills update\n```\n\nSupply the same target options used for installation. `status` is read-only and compares against the invoked CLI's bundled version; it does not query npm for newer releases. Repeated installation is idempotent. Updates check every installed file before changing any skill. Modified, added, deleted or unmanaged files cause the command to stop and list the conflicting folders; keep your customizations, move those folders outside the active skills directory, then retry. There is no force-overwrite option. A successful update returns backup paths outside the active skills directory for recovering the previous managed versions. Keep those backups until you have reviewed the update. Do not run concurrent writers: the installer lock coordinates other installer runs, but cannot lock an external editor.\n\nNo skills are installed by the repository build. Manual installation remains supported: copy whole folders from `skills/`, including references and scripts, to your agent's skill directory. Each folder is self-contained. The managed installer treats existing manual copies as unmanaged and preserves them.\n\nThe inspection helper requires Node 24 and `@openpresentation/opf` in the current project. In this checkout, build with `pnpm build` first. For an installed skill used outside the checkout, either run from an npm project that has the package or set `OPF_ROOT` to the built OPF checkout. It does not install dependencies, fetch catalogs, or modify input files.\n\n## Local CLI\n\nThe [installable CLI](../packages/cli/README.md) complements these skills with `opf create`, `opf validate`, `opf edit`, and schema/catalog lookup. Its tarball bundles the core schema and validator; the inspection skill helper instead resolves the host project's core package. Check versions when moving between them.\n\n## Examples of requests\n\n- \u201CUse $opf-author to turn these notes into a five-slide decision brief. Keep every factual claim sourced.\u201D\n- \u201CUse $opf-layout to fix this overflow without losing any text or notes.\u201D\n- \u201CUse $opf-presets to apply our brand colors while preserving slide-specific overrides.\u201D\n- \u201CUse $opf-edit to replace one table and retain all other document fields.\u201D\n- \u201CUse $opf-export to export the same reviewed slides to SVG and editable PPTX.\u201D\n- \u201CUse $opf-inspect to explain which background forms the installed schema accepts.\u201D\n\n## Maintenance\n\n`pnpm test:skills` checks skill links, schema-valid examples, and the inspection helper's actual behavior, including a copied standalone skill and a package installed in a consumer project. Run the skill-creator frontmatter validator when editing skill metadata. Behavioral tests are not evidence that every renderer option is visually complete.\n\nWhen schema/package APIs change, update only the affected skill/reference and its executable examples. Keep option lists in the canonical schema and catalogs. The format package and skill folders are separate distribution surfaces: CLI 0.5.0 includes the six skills; the core `@openpresentation/opf` package does not install agent configuration.\n"
7
+ "markdown": "# AI agent skills for OPF\n\nThe repository ships six reusable skills in `skills/`. Each folder has a `SKILL.md` entrypoint and optional references, assets, or scripts. `agents/openai.yaml` supplies Codex display metadata; the instructions themselves are Markdown and do not require a hosted service.\n\n| Skill | Use it for |\n| --- | --- |\n| [opf-author](../skills/opf-author/SKILL.md) | Turn briefs and source material into valid OPF content; includes a complete starter deck |\n| [opf-layout](../skills/opf-layout/SKILL.md) | Dynamic composition, nested groups, promoted regions, overflow repair, and pagination |\n| [opf-presets](../skills/opf-presets/SKILL.md) | Catalog discovery, design inheritance, gallery reuse, colors, themes, and fonts |\n| [opf-edit](../skills/opf-edit/SKILL.md) | Precise JSON Patch edits, undo, canvas/schema integration, and copy/import |\n| [opf-export](../skills/opf-export/SKILL.md) | Browser previews, SVG/PNG/PDF/PPTX, assets, fonts, and export verification |\n| [opf-inspect](../skills/opf-inspect/SKILL.md) | Exact schema fields, catalog IDs, validation errors, and reference warnings |\n\nLoad only the skills relevant to the request. They distinguish the portable format from current renderer/editor capabilities, and distinguish imported document instructions from the user's request. They do not authorize publishing, sending decks, or changing unrelated project configuration.\n\n## Use from a checkout\n\nAn agent can read the entrypoint directly, for example:\n\n> Use `skills/opf-author/SKILL.md` to create a decision brief in OPF, then validate it using `skills/opf-inspect/SKILL.md`.\n\nThe root `AGENTS.md` points repository agents to these entrypoints. Skills read the current project's schema and package exports instead of hardcoding a historical field count or assuming a public package has unreleased APIs.\n\n## Install in an agent environment\n\nPublished CLI 0.9.0 bundles all six complete skill folders. Use Node 24 for the current packages and this checkout. From your project directory:\n\n```sh\nnpx @openpresentation/cli@0.9.0 skills install\n```\n\nThe default installs copies into `.agents/skills` in the current project, suitable for agents including Codex. It does not change AGENTS.md or any agent configuration. No symlink privileges, paid service, API key or AI provider is required. npm downloads the CLI on first use; the installed CLI then installs its bundled skills without network access. The explicit 0.9.0 pin makes this installation repeatable and matches the [current package train](compatibility-matrix.md). For CLI source development, use `node packages/cli/dist/index.js skills install` after building.\n\n| Target | Project directory | Personal directory with `--global` |\n| --- | --- | --- |\n| Default / `--agent universal` | `.agents/skills` | `~/.agents/skills` |\n| `--agent codex` | `.agents/skills` | `~/.codex/skills` |\n| `--agent claude-code` | `.claude/skills` | `~/.claude/skills` |\n| `--agent cursor` | `.cursor/skills` | `~/.cursor/skills` |\n\nFor example, `npx @openpresentation/cli@0.9.0 skills install --agent codex --global` installs personal Codex skills. For another compatible agent use `--directory <its-skills-directory>`; this option cannot be combined with `--agent` or `--global`. Restart or reload your agent if its skill discovery requires it. A compatible client can invoke the installed skills with names such as `$opf-author` or `$opf-inspect`.\n\nInspect or update the same destination:\n\n```sh\nnpx @openpresentation/cli@0.9.0 skills status\nnpx @openpresentation/cli@0.9.0 skills update\n```\n\nSupply the same target options used for installation. `status` is read-only and compares against the invoked CLI's bundled version; it does not query npm for newer releases. Repeated installation is idempotent. Updates check every installed file before changing any skill. Modified, added, deleted or unmanaged files cause the command to stop and list the conflicting folders; keep your customizations, move those folders outside the active skills directory, then retry. There is no force-overwrite option. A successful update returns backup paths outside the active skills directory for recovering the previous managed versions. Keep those backups until you have reviewed the update. Do not run concurrent writers: the installer lock coordinates other installer runs, but cannot lock an external editor.\n\nNo skills are installed by the repository build. Manual installation remains supported: copy whole folders from `skills/`, including references and scripts, to your agent's skill directory. Each folder is self-contained. The managed installer treats existing manual copies as unmanaged and preserves them.\n\nThe inspection helper requires Node 24 and `@openpresentation/opf` in the current project. In this checkout, build with `pnpm build` first. For an installed skill used outside the checkout, either run from an npm project that has the package or set `OPF_ROOT` to the built OPF checkout. It does not install dependencies, fetch catalogs, or modify input files.\n\n## Local CLI\n\nThe [installable CLI](../packages/cli/README.md) complements these skills with `opf create`, `opf validate`, `opf edit`, and schema/catalog lookup. Its tarball bundles the core schema and validator; the inspection skill helper instead resolves the host project's core package. Check versions when moving between them.\n\n## Examples of requests\n\n- \u201CUse $opf-author to turn these notes into a five-slide decision brief. Keep every factual claim sourced.\u201D\n- \u201CUse $opf-layout to fix this overflow without losing any text or notes.\u201D\n- \u201CUse $opf-presets to apply our brand colors while preserving slide-specific overrides.\u201D\n- \u201CUse $opf-edit to replace one table and retain all other document fields.\u201D\n- \u201CUse $opf-export to export the same reviewed slides to SVG and editable PPTX.\u201D\n- \u201CUse $opf-inspect to explain which background forms the installed schema accepts.\u201D\n\n## Maintenance\n\n`pnpm test:skills` checks skill links, schema-valid examples, and the inspection helper's actual behavior, including a copied standalone skill and a package installed in a consumer project. Run the skill-creator frontmatter validator when editing skill metadata. Behavioral tests are not evidence that every renderer option is visually complete.\n\nWhen schema/package APIs change, update only the affected skill/reference and its executable examples. Keep option lists in the canonical schema and catalogs. The format package and skill folders are separate distribution surfaces: CLI 0.9.0 includes the six skills; the core `@openpresentation/opf` package does not install agent configuration. Repository skill prose can be newer than the immutable snapshot bundled in an already-published CLI. These source documentation corrections do not change the CLI 0.9.0 tarball; `skills status` and `skills update` compare against the invoked CLI's bundled snapshot.\n"
8
8
  },
9
9
  {
10
10
  "slug": "catalog-schema-reference",
11
11
  "file": "docs/catalog-schema-reference.md",
12
12
  "title": "OPF Catalog Schema Reference",
13
- "markdown": "# OPF Catalog Schema Reference\n\nCatalog records are reusable presets that OPF documents reference by id. This page summarizes every companion schema in `spec/schemas/` except the top-level presentation schema.\n\nOPF documents usually reference these records with string ids such as `design.theme = \"minimal\"`, `tone = \"formal\"`, or `chart.type = \"line\"`. Dense examples may also embed catalog sources or inline records under `catalogs`.\n\n## Audience\n\n- File: `spec/schemas/audience.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-audience/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for audience records in the pptx.gallery library. Each record names an audience archetype (e.g. 'executives', 'engineering-team', 'investors') and carries seniority, technical-fluency, decision-power, and attention-budget hints used by AI-driven generation. Audiences are referenced from OPF documents via audience; the engine resolves the reference against catalogs.audiences (inline) catalogs.audiences.source the default catalog at https://www.pptx.gallery/audiences. The audience field...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-audience/v1\"` | Identifies this record as an audience in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this audience via audience. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable audience name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the audience who they are and what they care about. |\n| `description` | no | `string` | Longer prose describing the audience archetype and how to address them. |\n| `seniority` | no | `enum:ic \\| manager \\| director \\| vp \\| c-suite \\| mixed` | Typical seniority level of the audience. Engines use this as a hint for default depth and pacing. |\n| `technicalFluency` | no | `enum:low \\| medium \\| high \\| mixed` | Typical technical fluency of the audience. AI generation uses this to decide whether to expand or assume technical terminology. |\n| `decisionPower` | no | `enum:informational \\| advisory \\| decision-maker` | Whether the audience is expected to be informed, to advise, or to actually decide. Shapes the strength of the closing ask. |\n| `attentionBudgetMinutes` | no | `number` | Realistic upper bound on this audience's focused attention for a single presentation, in minutes. Used as a hint when comparing against duration and the resolved narrative's durationRange. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this audience. Used by picker UIs to suggest narratives once an audience is chosen. Validators warn on unknown ids; never error. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this audience. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n\n## Catalog Index\n\n- File: `spec/schemas/catalog-index.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-catalog-index/v1`\n- Type: `object`\n- Required fields: `$schema`, `version`, `description`, `records`\n- Purpose: Generic shape shared by every `spec/catalogs/<kind>/index.json` file in the OPF repo. An index is a lightweight, ordered summary of the full-record JSON files that live alongside it: each entry names the record's stable id, a human-readable name, and the record's filename, plus whatever extra summary fields are useful for picker UIs (e.g. `summary`, `tags`, `bcp47`, `durationRange`). This schema describes the repo-internal catalog index files themselves, not OPF documents or individual catalo...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-catalog-index/v1\"` | |\n| `version` | yes | `string` | Index format version, as a string. |\n| `description` | yes | `string` | Human-readable description of what this catalog kind holds and how entries are ordered. |\n| `records` | yes | `array<ref:IndexRecord>` | Ordered list of lightweight record summaries. Order defines the catalog's canonical/display order; full record data lives in the sibling JSON file named by `file`. |\n\n### Nested Types\n\n#### IndexRecord\n\n- Type: `object`\n- Required fields: `id`, `name`, `file`\n- Purpose: Lightweight summary of one catalog record. Additional per-kind fields (e.g. `summary`, `tags`, `bcp47`, `durationRange`, `group`, `label`) are allowed and vary by catalog kind.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier, matching the `id` field inside the record file named by `file`. |\n| `name` | yes | `string` | Human-readable name shown in pickers. |\n| `file` | yes | `string` | Filename of the full record, relative to this index file's directory. |\n\n## Chart Type\n\n- File: `spec/schemas/chart-type.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-chart-type/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `mappings`\n- Purpose: Schema for chart-type records in the pptx.gallery catalog. Each record describes a named chart variant, its Open XML mapping, its series/category cardinality, the column structure of the underlying workbook, and a small sample dataset suitable for previews. Chart types are referenced from OPF chart content payloads; the engine resolves the reference against catalogs.chartTypes (inline) -> catalogs.chartTypes.source -> the default catalog at https://www.pptx.gallery/chart-types.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-chart-type/v1\"` | Identifies this record as a chart type in the open presentation catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this chart type. Lowercase kebab-case. Chart type ids may start with a digit (e.g., '100pct-stacked-column', '3d-column') to mirror conventional chart naming. |\n| `name` | yes | `string` | Stable display/programmatic name for this chart type. |\n| `label` | no | `string` | Human-readable label shown in chart pickers. |\n| `summary` | no | `string` | One-sentence positioning: when to reach for this chart variant. |\n| `description` | no | `string` | Longer prose describing the chart and ideal use cases. |\n| `mappings` | yes | `ref:ChartTypeMappings` | Canonical and optional renderer-specific mappings used by engines to render this chart type. |\n| `group` | no | `string` | Top-level grouping in the chart picker (column, bar, line, area, pie, radar, etc.). |\n| `groupSort` | no | `integer` | Display ordering hint within the chart group. |\n| `complexity` | no | `enum:simple \\| calculated \\| hierarchical \\| normalized` | Shape of the underlying data: a flat series ('simple'), one with engine-side calculation ('calculated'), parent-child rows ('hierarchical'), or pre-normalized rows ('normalized'). |\n| `series` | no | `integer` | Number of data series this chart type expects. |\n| `categories` | no | `integer` | Number of category labels this chart type expects on the primary axis. |\n| `seriesGroups` | no | `integer` | Number of series groups (axis bands) this chart type uses; >1 for combo or banded charts. |\n| `useSecondaryCategories` | no | `boolean` | Whether the chart type uses a secondary category axis. |\n| `workbookRange` | no | `string` | A1 reference to the source range in the embedded workbook. |\n| `columns` | no | `array<string>` | Column header names of the embedded workbook, in left-to-right order. |\n| `dataColumns` | no | `array<ref:ChartDataColumn>` | Per-column metadata describing the role and position of each column in the workbook source. |\n| `helperColumns` | no | `array<string>` | Optional auxiliary column names used by calculated or banded charts (e.g., 'Excellent', 'Good', 'Fair', 'Poor' for a bullet chart). |\n| `sampleData` | no | `ref:ChartSampleData` | Inline sample dataset for previews and pickers. |\n| `slideNumber` | no | `integer` | Source slide number in the original chart-gallery deck. Carried for traceability. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n### Nested Types\n\n#### ChartTypeMappings\n\n- Type: `object`\n- Required fields: `openxml`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `openxml` | yes | `ref:OpenXmlChartMapping` | Canonical mapping to Open XML chart structures. |\n| `renderers` | no | `object` | Optional renderer-specific mappings. Keys are renderer ids; values are intentionally opaque to OPF. |\n\n#### OpenXmlChartMapping\n\n- Type: `object`\n- Required fields: none\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `element` | no | `string` | Primary Open XML chart element or extension chart element, such as 'barChart', 'lineChart', 'pieChart', 'treemapChart', or 'waterfallChart'. |\n| `barDir` | no | `enum:bar \\| col` | Bar direction for Open XML barChart mappings. |\n| `grouping` | no | `enum:standard \\| clustered \\| stacked \\| percentStacked` | Open XML chart grouping value when the chart family supports grouping. |\n| `marker` | no | `boolean` | Whether the chart type expects visible data markers. |\n| `radarStyle` | no | `enum:standard \\| marker \\| filled` | Open XML radarStyle value for radarChart mappings. |\n| `scatterStyle` | no | `enum:line \\| lineMarker \\| marker \\| smooth \\| smoothMarker` | Open XML scatterStyle value for scatterChart mappings. |\n| `composition` | no | `enum:single \\| mixed \\| extension` | Whether the chart maps to one standard chart element, multiple combined chart elements, or an Open XML extension chart. |\n| `extension` | no | `string` | Optional Open XML extension namespace or element hint for extension charts. |\n| `series` | no | `array<ref:OpenXmlChartMapping>` | Open XML chart elements used by mixed/composite chart types. |\n| `notes` | no | `string` | Short implementation note for mappings that need renderer interpretation. |\n\n#### ChartDataColumn\n\n- Type: `object`\n- Required fields: `name`, `role`, `type`\n- Purpose: One column of the embedded chart workbook, annotated with its role and grid position.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `name` | yes | `string` | Column header name (e.g. 'Series 1', 'Value', 'Level1', 'Level2'). |\n| `role` | yes | `enum:categoryLabel \\| series \\| helper` | Role this column plays: a category label (axis tick), a series (plotted values), or a helper (calculated/auxiliary). |\n| `type` | yes | `enum:string \\| number` | Cell value type for the column. |\n| `position` | no | `string` | Grid position of the column header in the source workbook, as 'row<N>_col<M>' (zero-indexed). |\n\n#### ChartSampleData\n\n- Type: `object`\n- Required fields: `headers`, `rows`\n- Purpose: Inline sample dataset for previews. Mirrors a small workbook with header row plus data rows.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `headers` | yes | `array<string>` | Header row labels. The first cell typically labels the series column; the rest are category labels. |\n| `rows` | yes | `array<array<string \\| number>>` | Two-dimensional sample data. Each row aligns by index with the headers first cell is the row label, remaining cells are values. |\n\n## Color Scheme\n\n- File: `spec/schemas/color-scheme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-color-scheme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for color-scheme records in the pptx.gallery library. Each scheme is a named palette with the twelve PowerPoint color slots (six accents, two darks, two lights, plus hyperlink and followed-hyperlink), suitable for being mapped directly into OOXML theme XML. Color schemes are referenced from OPF documents via design.colorScheme or design.colorScheme.id; the engine resolves the reference against catalogs.colorSchemes (inline) -> catalogs.colorSchemes.source -> the default catalog at http...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-color-scheme/v1\"` | Identifies this record as a color scheme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this color scheme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable scheme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the palette what mood it evokes and where to use it. |\n| `description` | no | `string` | Longer prose describing the palette and its intended use. |\n| `accent1` | no | `string` | Accent 1 color (hex). Mirrors the OOXML accent1 slot. |\n| `accent2` | no | `string` | Accent 2 color (hex). Mirrors the OOXML accent2 slot. |\n| `accent3` | no | `string` | Accent 3 color (hex). Mirrors the OOXML accent3 slot. |\n| `accent4` | no | `string` | Accent 4 color (hex). Mirrors the OOXML accent4 slot. |\n| `accent5` | no | `string` | Accent 5 color (hex). Mirrors the OOXML accent5 slot. |\n| `accent6` | no | `string` | Accent 6 color (hex). Mirrors the OOXML accent6 slot. |\n| `dark1` | no | `string` | Dark 1 color (hex). Typically the deepest neutral; OOXML dark1. |\n| `dark2` | no | `string` | Dark 2 color (hex). Secondary dark; OOXML dark2. |\n| `light1` | no | `string` | Light 1 color (hex). Typically the slide canvas; OOXML lt1. |\n| `light2` | no | `string` | Light 2 color (hex). Secondary light surface; OOXML lt2. |\n| `hyperlink` | no | `string` | Hyperlink color (hex). OOXML hlink. |\n| `followedHyperlink` | no | `string` | Followed-hyperlink color (hex). OOXML folHlink. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Font Scheme\n\n- File: `spec/schemas/font-scheme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-font-scheme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `major`, `minor`\n- Purpose: Schema for font-scheme records in the pptx.gallery library. Each scheme pairs a major (heading) and minor (body) font family in the OOXML majorFont/minorFont sense, scoped to a target app (PowerPoint or Google Slides) and a language family (Latin, East Asian, or Complex Script). Font schemes are referenced from OPF documents via design.fontScheme or design.fontScheme.id; the engine resolves the reference against catalogs.fontSchemes (inline) catalogs.fontSchemes.source the default catalog at...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-font-scheme/v1\"` | Identifies this record as a font scheme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this font scheme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable scheme name shown in pickers. |\n| `major` | yes | `string` | Heading (major) font family mirrors the OOXML majorFont entry. |\n| `minor` | yes | `string` | Body (minor) font family mirrors the OOXML minorFont entry. |\n| `type` | no | `enum:sans-serif \\| serif \\| monospace` | High-level typographic class of the scheme. |\n| `app` | no | `enum:PowerPoint \\| Google Slides` | Target application this font pairing is intended for. |\n| `languageFamily` | no | `enum:latin \\| ea \\| cs` | OOXML font-language family this scheme is intended for: 'latin' for Latin-script content, 'ea' for East Asian scripts, 'cs' for Complex Scripts. |\n| `languages` | no | `array<string>` | Optional list of human-readable language names this scheme is curated for. Useful for picker UIs that group fonts by language coverage. |\n| `textSample` | no | `string` | Short specimen string used by picker UIs to preview the scheme. |\n| `summary` | no | `string` | One-sentence positioning of the font pairing. |\n| `description` | no | `string` | Longer prose describing the font scheme and where it shines. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Language\n\n- File: `spec/schemas/language.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-language/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `bcp47`\n- Purpose: Schema for language records in the pptx.gallery library. Each record names a presentation language, carries a BCP-47 language tag, and pairs it with sensible default font schemes for PowerPoint and Google Slides output. Languages are referenced from OPF documents via language; the engine resolves the reference against catalogs.languages (inline) catalogs.languages.source the default catalog at https://www.pptx.gallery/languages. The presentation language field also accepts BCP-47 tags directl...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-language/v1\"` | Identifies this record as a language in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this language via language. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable language name. |\n| `code` | no | `string` | ISO 639-3 (or 639-2) three-letter language code. Carried for engines that prefer ISO codes. |\n| `bcp47` | yes | `string` | BCP-47 language tag for this record. Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region form. |\n| `direction` | no | `enum:ltr \\| rtl` | Base text direction for the language. |\n| `script` | no | `string` | ISO 15924 script code when the writing system should be explicit. |\n| `fontScheme` | no | `string` | Default font-scheme id for this language when targeting PowerPoint output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. |\n| `googleFontScheme` | no | `string` | Default font-scheme id for this language when targeting Google Slides output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. |\n| `summary` | no | `string` | One-sentence note about coverage or font defaults. |\n| `description` | no | `string` | Longer prose describing the language record and any font-pairing rationale. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Layout Preview Index\n\n- File: `spec/schemas/layout-preview-index.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-layout-preview-index/v1`\n- Type: `object`\n- Required fields: `$schema`, `version`, `description`, `records`\n- Purpose: Shape of `spec/previews/layouts/index.json`, the manifest for the vendored slide-archetype preview gallery under `spec/previews/layouts/`. Each record names a preview id, its self-contained HTML file, and the file's exact UTF-8 byte length. These preview ids are an archetype taxonomy (e.g. 'swot-analysis', 'org-chart') distinct from the structural layout catalog at spec/catalogs/layouts/ (e.g. 'title', 'chart-2x') see spec/README.md. This schema describes a repo-internal index file, not an OP...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-layout-preview-index/v1\"` | |\n| `version` | yes | `string` | Index format version, as a string. |\n| `description` | yes | `string` | Human-readable description of the preview gallery and its rendering conventions. |\n| `records` | yes | `array<ref:PreviewRecord>` | One entry per vendored preview HTML file. |\n\n### Nested Types\n\n#### PreviewRecord\n\n- Type: `object`\n- Required fields: `id`, `file`, `bytes`\n- Purpose: Summary of one vendored preview HTML file.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Slide-archetype preview id (e.g. 'swot-analysis', 'agenda', 'org-chart'). Does not correspond to a spec/catalogs/layouts/ record id. |\n| `file` | yes | `string` | HTML filename, relative to this index file's directory. |\n| `bytes` | yes | `integer` | Exact UTF-8 byte length of the referenced HTML file's contents. |\n\n## Slide Layout\n\n- File: `spec/schemas/layout.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-layout/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for slide-layout records in the pptx.gallery library. Each record describes a semantic slide layout what regions it exposes and what content kinds those regions are intended to hold. Layouts are referenced from OPF documents via Slide.layout; the engine resolves the reference against catalogs.layouts (inline) catalogs.layouts.source the default catalog at https://www.pptx.gallery/layouts. Free-form custom layout names that don't resolve through any catalog fall through to engine-define...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-layout/v1\"` | Identifies this record as a slide layout in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this layout via Slide.layout. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable layout name shown in layout pickers. |\n| `summary` | no | `string` | One-sentence positioning of the layout when to reach for it. |\n| `description` | no | `string` | Longer prose describing the layout structure and ideal use cases. |\n| `contentType` | no | `enum:Title \\| Text \\| List \\| Image \\| Number \\| Metric \\| Chart \\| Table \\| Code \\| Video \\| Quote \\| Timeline` | Primary kind of content the layout holds. Drives pickers and AI placement decisions. Metric is the canonical numeric/KPI category; Number remains an accepted legacy label. |\n| `contentMultiple` | no | `enum:None \\| 1x \\| 2x \\| 3x \\| 4x \\| 5x \\| 6x` | How many parallel content blocks the layout exposes ('2x' = two-column, '3x' = three-up, etc.). |\n| `contentAlignment` | no | `enum:None \\| Left \\| Center` | Default horizontal alignment of the content area. |\n| `contentBox` | no | `boolean` | Whether the content area is rendered inside a visible box / card. |\n| `contentTypeChartPrimary` | no | `enum:None \\| Top \\| Bottom \\| Left \\| Right` | For chart layouts, where the primary chart sits relative to the rest of the content. |\n| `contentTypeImageFill` | no | `enum:None \\| Crop \\| Fit` | For image layouts, how the image fills its slot. |\n| `contentTypeListBullet` | no | `enum:None \\| Character \\| Image` | For list layouts, how bullets are rendered. |\n| `contentTypeListHeading` | no | `boolean` | For list layouts, whether each list item carries a heading. |\n| `slideTag` | no | `boolean` | Whether the layout includes a small slide-level tag / label region above or near the title. |\n| `slideTitle` | no | `boolean` | Whether the layout includes a slide title region. |\n| `slideSubtitle` | no | `boolean` | Whether the layout includes a slide-level subtitle or supporting-description region. When placeholders is present, this is true exactly when the layout exposes a placeholder with type 'subtitle'. |\n| `slideTitleAlignment` | no | `enum:None \\| Left \\| Center` | Horizontal alignment of the slide title region. |\n| `slideImage` | no | `boolean` | Whether the layout includes a dedicated slide-level image region (separate from any content image). |\n| `slideImageAlignment` | no | `enum:None \\| Top \\| Bottom \\| Left \\| Right \\| Background` | Where the slide-level image sits relative to the content. |\n| `slideLayoutDirection` | no | `enum:None \\| Horizontal \\| Vertical` | Axis along which the layout's primary regions are arranged. |\n| `placeholders` | no | `array<ref:Placeholder>` | Ordered regions the layout exposes. The engine fills 'title', 'subtitle', and 'tag' placeholders from Slide.title, Slide.subtitle, and Slide.tag. Other placeholders are content-kind hints for renderers and pickers. Sl... |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n| `composition` | no | `ref:Composition` | |\n\n### Nested Types\n\n#### Placeholder\n\n- Type: `object`\n- Required fields: `type`\n- Purpose: A single region inside a slide layout. Title, subtitle, and tag placeholders bind to the corresponding Slide fields; other placeholders describe the intended content kind for that region. The array order in the surrounding 'placeholders' field preserves layout region order.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `enum:title \\| subtitle \\| tag \\| text \\| metric \\| quote \\| timeline \\| list \\| chart \\| picture \\| table \\| media \\| diagram \\| code` | OPF placeholder kind. 'text' and 'list' are flexible textual content regions. 'metric' is a numeric/KPI content region filled by a metric payload, including its optional label, description, unit, delta, and trend. The... |\n\n#### Composition\n\n- Type: `object`\n- Required fields: none\n- Purpose: Portable dynamic composition. Slide fields override the resolved layout. Nested groups arrange their children independently, inheriting only minFontSize and overflow. Explicit promoted regions retain their positions.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `mode` | no | `enum:auto \\| grid \\| row \\| column` | auto chooses a grid from available space and content; grid uses columns; row and column use one horizontal or vertical track. |\n| `columns` | no | `integer` | Column count for grid. In auto mode this caps the number of columns. |\n| `gap` | no | `number` | Space between cells as a fraction of the container short edge (canvas at slide root). Default 0.03333333333333333. |\n| `padding` | no | `number` | Inset as a fraction of the container short edge. Default 0.08 on a slide, 0 inside a group. |\n| `weights` | no | `array<number>` | Relative track sizes: columns for row/grid/auto, rows for column. Omitted tracks have weight 1; extra weights are ignored. |\n| `minFontSize` | no | `number` | Minimum readable text size in reference pixels at a 720-pixel canvas short edge. Default 16. Overflow is diagnosed when text cannot fit at this size. |\n| `overflow` | no | `enum:warn \\| error` | warn returns diagnostics for content that does not fit; error rejects layout. Content is never silently removed. Default warn. |\n\n## Narrative Template\n\n- File: `spec/schemas/narrative.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-narrative/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `beats`\n- Purpose: Schema for narrative template files in the openpresentation.org catalog. Each template describes a named story arc (e.g. 'problem-solution', 'scqa') as an ordered list of beats. Templates are referenced from OPF documents via narrative either as a bare id string (e.g. 'classic-story') or as an inline object whose shape matches this schema (sans '$schema').\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-narrative/v1\"` | |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this template, e.g. 'problem-solution'. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable template name, e.g. 'Problem Solution'. |\n| `summary` | no | `string` | One-sentence description of when and why to use this narrative. |\n| `description` | no | `string` | Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction. |\n| `audienceFit` | no | `array<string>` | Audiences this narrative works well for, e.g. ['executives', 'investors', 'customers']. |\n| `durationRange` | no | `object` | Typical talk-length window this narrative suits. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search, e.g. ['business', 'pitch', 'internal']. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n| `beats` | yes | `array<ref:Beat>` | Ordered list of beats that make up the narrative arc. |\n\n### Nested Types\n\n#### Beat\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A single narrative beat a labeled segment of the story arc with a specific dramatic purpose. Mirrors the NarrativeBeat definition in opf.schema.json so library entries and inline OPF beats are interchangeable.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable beat name, e.g. 'The Problem'. |\n| `description` | no | `string` | Curator-written prose that explains what this beat should accomplish. |\n| `instructions` | no | `string` | Short author-facing instruction for the beat typically one phrase. Complements 'description' with a concise directive. |\n| `slideCount` | no | `integer` | Optional explicit slide count for this beat. Defaults to 1 when omitted; values >1 are reserved for beats that intentionally span multiple slides. Prefer decomposing a heavy beat into multiple beats over setting a hig... |\n| `slideType` | no | `enum:text \\| list \\| image \\| shape \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Default content kind for the beat's slide. Uses ContentPayload.type names to help engines choose a layout. The legacy shape value is retained for compatibility and requests an image representation; it is not a native... |\n| `layoutHint` | no | `string` | Suggested layout id for the beat's opening slide, e.g. 'section-divider', 'title-slide', 'text-left'. Resolves the same way as Slide.layout against catalogs.layouts and the default catalog at https://www.pptx.gallery/... |\n| `thoughtCues` | no | `array<string>` | Optional speaker or thinking cues attached to the beat. Surfaced in presenter notes. |\n\n## Purpose\n\n- File: `spec/schemas/purpose.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-purpose/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for purpose records in the pptx.gallery library. Each record names a presentation objective such as informing, aligning, persuading, driving a decision, or selling. Purposes are referenced from OPF documents via purpose; the engine resolves the reference against catalogs.purposes (inline) catalogs.purposes.source the default catalog at https://www.pptx.gallery/purposes. The purpose field also accepts free-form strings and inline Purpose objects.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-purpose/v1\"` | Identifies this record as a purpose in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this purpose via purpose. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable purpose name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the purpose what this deck is trying to accomplish. |\n| `description` | no | `string` | Longer prose describing when to use this purpose and how it should shape a deck. |\n| `outcome` | no | `string` | Desired audience outcome after the presentation. |\n| `successCriteria` | no | `array<string>` | Observable signals that the deck accomplished this purpose. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this purpose. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this purpose. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n\n## Social Platform\n\n- File: `spec/schemas/social-platform.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-social-platform/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for social-platform records in the pptx.gallery library. Each record describes a single social-media platform its base URL, profile-URL pattern, handle prefix, brand color, and themed icons. Records are referenced from OPF documents indirectly: the property keys of any Socials object (Organization.socials, Speaker.socials) match record ids, and renderers use the catalog record to format URLs and pick icons. The engine resolves references against catalogs.socialPlatforms (inline) catalo...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-social-platform/v1\"` | Identifies this record as a social-platform entry in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this platform appears as a property key on Socials objects. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable platform name shown in pickers and footers. |\n| `summary` | no | `string` | One-sentence positioning of the platform what it's used for and who's on it. |\n| `description` | no | `string` | Longer prose describing the platform and any rendering conventions (e.g., handle prefixes, distributed instances). |\n| `baseUrl` | no | `string` | Canonical base URL of the platform used as the prefix when normalizing handles to full URLs. |\n| `profileUrlPattern` | no | `string` | URL pattern for individual member profiles. Use '{handle}' as the placeholder for the handle (with the prefix already stripped). |\n| `companyUrlPattern` | no | `string` | Optional URL pattern for organization / company pages, when the platform distinguishes them from member profiles. Use '{handle}' as the placeholder. |\n| `handlePrefix` | no | `string` | Conventional prefix character displayed before the handle (e.g. '@' for X / Mastodon / Threads / TikTok). Empty string when no prefix is used. Renderers strip it before substituting into URL patterns. |\n| `handleExample` | no | `string` | Example handle in its conventional rendered form, used by picker UIs and validation hints. |\n| `brandColor` | no | `string` | Brand color (hex) used for branded icon chips, link styling, or section accents. |\n| `icon` | no | `string` | Default icon source. Accepts an HTTPS URL, data URI, relative path, or asset reference. Used as the fallback when a themed (Light/Dark) variant isn't set. |\n| `iconLight` | no | `string` | Light-colored icon variant intended for rendering on dark backgrounds. |\n| `iconDark` | no | `string` | Dark-colored icon variant intended for rendering on light backgrounds. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Theme\n\n- File: `spec/schemas/theme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-theme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for theme records in the pptx.gallery library. Each theme is a small, named bundle that pairs a color scheme, a font scheme, a default theme-controlled background, and a slide size. Themes are referenced from OPF documents via design.theme or design.theme.id; the engine resolves the reference against catalogs.themes (inline) catalogs.themes.source the default catalog at https://www.pptx.gallery/themes. Inline overrides on design.colorScheme / design.fontScheme / design.background / des...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-theme/v1\"` | Identifies this record as a theme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this theme via design.theme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable theme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the theme when to reach for it. |\n| `description` | no | `string` | Longer prose describing what the theme looks and feels like and the kinds of decks it suits. |\n| `colorScheme` | no | `string` | Catalog reference to the theme's default color scheme resolved against catalogs.colorSchemes the same way design.colorScheme or design.colorScheme.id is. Accepts a bare id, HTTPS URL, or 'pkg:' reference. |\n| `fontScheme` | no | `string` | Catalog reference to the theme's default font scheme resolved against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id is. Accepts a bare id, HTTPS URL, or 'pkg:' reference. |\n| `background` | no | `ref:ThemeBackground` | |\n| `dimensions` | no | `enum:16:9 \\| 4:3 \\| 16:10 \\| letter \\| a4 \\| widescreen \\| standard` | Default slide size for this theme. Accepts the same preset values as design.dimensions.preset. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n### Nested Types\n\n#### ThemeBackgroundSlot\n\n- Type: `enum:light1 | light2 | dark1 | dark2`\n- Required fields: none\n- Purpose: PowerPoint theme-controlled slide background slot from the active color scheme. These are slots, not assumptions about actual colors: light1 is usually white and dark1 is usually black by convention, but the color scheme controls the real values.\n\n_No named properties._\n\n#### ThemeBackground\n\n- Type: `object`\n- Required fields: `type`, `slot`\n- Purpose: Theme-controlled PowerPoint slide background. The slot is resolved through the active color scheme and remains theme-aware.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"theme\"` | Theme-controlled background fill. |\n| `slot` | yes | `ref:ThemeBackgroundSlot` | |\n\n## Tone\n\n- File: `spec/schemas/tone.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-tone/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for tone records in the pptx.gallery library. Each record names a presentation tone (e.g. 'formal', 'casual', 'inspirational') and carries voice cues, anti-patterns, and sample phrases that AI-driven generation uses to shape output. Tones are referenced from OPF documents via tone; the engine resolves the reference against catalogs.tones (inline) catalogs.tones.source the default catalog at https://www.pptx.gallery/tones.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-tone/v1\"` | Identifies this record as a tone in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this tone via tone. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable tone name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the tone when to reach for it. |\n| `description` | no | `string` | Longer prose describing the tone and the kinds of decks it suits. |\n| `voiceCues` | no | `array<string>` | Short directives that shape AI generation toward this tone. Phrased as imperatives, e.g. 'use second-person', 'favor short sentences', 'lead with the recommendation'. |\n| `avoid` | no | `array<string>` | Anti-patterns that AI generation should not produce when this tone is active. |\n| `samplePhrases` | no | `array<string>` | Short example phrases that exemplify this tone. Used by picker UIs and as few-shot examples for AI generation. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids this tone pairs well with. Used by picker UIs to suggest narratives once a tone is chosen. Validators warn on unknown ids; never error. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n"
13
+ "markdown": "# OPF Catalog Schema Reference\n\nCatalog records are reusable presets that OPF documents reference by id. This page summarizes every companion schema in `spec/schemas/` except the top-level presentation schema.\n\nOPF documents usually reference these records with string ids such as `design.theme = \"minimal\"`, `tone = \"formal\"`, or `chart.type = \"line\"`. Dense examples may also embed catalog sources or inline records under `catalogs`.\n\n## Audience\n\n- File: `spec/schemas/audience.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-audience/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for audience records in the pptx.gallery library. Each record names an audience archetype (e.g. 'executives', 'engineering-team', 'investors') and carries seniority, technical-fluency, decision-power, and attention-budget hints used by AI-driven generation. Audiences are referenced from OPF documents via audience; the engine resolves the reference against catalogs.audiences (inline) catalogs.audiences.source the default catalog at https://www.pptx.gallery/audiences. The audience field...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-audience/v1\"` | Identifies this record as an audience in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this audience via audience. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable audience name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the audience who they are and what they care about. |\n| `description` | no | `string` | Longer prose describing the audience archetype and how to address them. |\n| `seniority` | no | `enum:ic \\| manager \\| director \\| vp \\| c-suite \\| mixed` | Typical seniority level of the audience. Engines use this as a hint for default depth and pacing. |\n| `technicalFluency` | no | `enum:low \\| medium \\| high \\| mixed` | Typical technical fluency of the audience. AI generation uses this to decide whether to expand or assume technical terminology. |\n| `decisionPower` | no | `enum:informational \\| advisory \\| decision-maker` | Whether the audience is expected to be informed, to advise, or to actually decide. Shapes the strength of the closing ask. |\n| `attentionBudgetMinutes` | no | `number` | Realistic upper bound on this audience's focused attention for a single presentation, in minutes. Used as a hint when comparing against duration and the resolved narrative's durationRange. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this audience. Used by picker UIs to suggest narratives once an audience is chosen. Validators warn on unknown ids; never error. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this audience. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n\n## Catalog Index\n\n- File: `spec/schemas/catalog-index.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-catalog-index/v1`\n- Type: `object`\n- Required fields: `$schema`, `version`, `description`, `records`\n- Purpose: Generic shape shared by every `spec/catalogs/<kind>/index.json` file in the OPF repo. An index is a lightweight, ordered summary of the full-record JSON files that live alongside it: each entry names the record's stable id, a human-readable name, and the record's filename, plus whatever extra summary fields are useful for picker UIs (e.g. `summary`, `tags`, `bcp47`, `durationRange`). This schema describes the repo-internal catalog index files themselves, not OPF documents or individual catalo...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-catalog-index/v1\"` | |\n| `version` | yes | `string` | Index format version, as a string. |\n| `description` | yes | `string` | Human-readable description of what this catalog kind holds and how entries are ordered. |\n| `records` | yes | `array<ref:IndexRecord>` | Ordered list of lightweight record summaries. Order defines the catalog's canonical/display order; full record data lives in the sibling JSON file named by `file`. |\n\n### Nested Types\n\n#### IndexRecord\n\n- Type: `object`\n- Required fields: `id`, `name`, `file`\n- Purpose: Lightweight summary of one catalog record. Additional per-kind fields (e.g. `summary`, `tags`, `bcp47`, `durationRange`, `group`, `label`) are allowed and vary by catalog kind.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier, matching the `id` field inside the record file named by `file`. |\n| `name` | yes | `string` | Human-readable name shown in pickers. |\n| `file` | yes | `string` | Filename of the full record, relative to this index file's directory. |\n\n## Chart Type\n\n- File: `spec/schemas/chart-type.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-chart-type/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `mappings`\n- Purpose: Schema for chart-type records in the pptx.gallery catalog. The bundled catalog holds one record per chart type that Aspose.Slides officially supports (see mappings.renderers[\"aspose-slides\"].chartType). Each record describes a named chart variant, its Open XML mapping, its series/category cardinality, the column structure of the underlying workbook, and a small sample dataset suitable for previews. Chart types are referenced from OPF chart content payloads; the engine resolves the reference a...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-chart-type/v1\"` | Identifies this record as a chart type in the open presentation catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this chart type. Lowercase kebab-case. Chart type ids may start with a digit (e.g., '100pct-stacked-column', '3d-column') to mirror conventional chart naming. |\n| `name` | yes | `string` | Stable display/programmatic name for this chart type. |\n| `label` | no | `string` | Human-readable label shown in chart pickers. |\n| `summary` | no | `string` | One-sentence positioning: when to reach for this chart variant. |\n| `description` | no | `string` | Longer prose describing the chart and ideal use cases. |\n| `mappings` | yes | `ref:ChartTypeMappings` | Canonical and optional renderer-specific mappings used by engines to render this chart type. |\n| `deprecation` | no | `ref:ChartTypeDeprecation` | Present when this chart type is deprecated. Deprecated records stay resolvable so existing documents keep validating, but pickers and default listings exclude them, validators warn when a document references them, and... |\n| `group` | no | `string` | Top-level grouping in the chart picker (column, bar, line, area, pie, radar, etc.). |\n| `groupSort` | no | `integer` | Display ordering hint within the chart group. |\n| `complexity` | no | `enum:simple \\| calculated \\| hierarchical \\| normalized` | Shape of the underlying data: a flat series ('simple'), one with engine-side calculation ('calculated'), parent-child rows ('hierarchical'), or pre-normalized rows ('normalized'). |\n| `series` | no | `integer` | Number of data series this chart type expects. |\n| `categories` | no | `integer` | Number of category labels this chart type expects on the primary axis. |\n| `seriesGroups` | no | `integer` | Number of series groups (axis bands) this chart type uses; >1 for combo or banded charts. |\n| `useSecondaryCategories` | no | `boolean` | Whether the chart type uses a secondary category axis. |\n| `workbookRange` | no | `string` | A1 reference to the source range in the embedded workbook. |\n| `columns` | no | `array<string>` | Column header names of the embedded workbook, in left-to-right order. |\n| `dataColumns` | no | `array<ref:ChartDataColumn>` | Per-column metadata describing the role and position of each column in the workbook source. |\n| `helperColumns` | no | `array<string>` | Optional auxiliary column names used by calculated or banded charts (e.g., 'Excellent', 'Good', 'Fair', 'Poor' for a bullet chart). |\n| `sampleData` | no | `ref:ChartSampleData` | Inline sample dataset for previews and pickers. |\n| `slideNumber` | no | `integer` | Source slide number in the original chart-gallery deck. Carried for traceability. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n### Nested Types\n\n#### ChartTypeDeprecation\n\n- Type: `object`\n- Required fields: `replacedBy`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `replacedBy` | yes | `string` | Id of the non-deprecated chart type that documents should reference instead. |\n| `reason` | no | `string` | Why the record is deprecated. |\n| `removal` | no | `string` | Package version in which the record is scheduled for removal from the bundled catalog. |\n\n#### ChartTypeMappings\n\n- Type: `object`\n- Required fields: `openxml`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `openxml` | yes | `ref:OpenXmlChartMapping` | Canonical mapping to Open XML chart structures. |\n| `renderers` | no | `object` | Optional renderer-specific mappings. Keys are renderer ids; values are intentionally opaque to OPF. The bundled catalog records the matching Aspose.Slides ChartType enumeration member under the \"aspose-slides\" key, e.... |\n\n#### OpenXmlChartMapping\n\n- Type: `object`\n- Required fields: none\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `element` | no | `string` | Primary Open XML chart element or extension chart element, such as 'barChart', 'lineChart', 'pieChart', 'treemapChart', or 'waterfallChart'. |\n| `barDir` | no | `enum:bar \\| col` | Bar direction for Open XML barChart mappings. |\n| `grouping` | no | `enum:standard \\| clustered \\| stacked \\| percentStacked` | Open XML chart grouping value when the chart family supports grouping. |\n| `marker` | no | `boolean` | Whether the chart type expects visible data markers. |\n| `radarStyle` | no | `enum:standard \\| marker \\| filled` | Open XML radarStyle value for radarChart mappings. |\n| `scatterStyle` | no | `enum:line \\| lineMarker \\| marker \\| smooth \\| smoothMarker` | Open XML scatterStyle value for scatterChart mappings. |\n| `composition` | no | `enum:single \\| mixed \\| extension` | Whether the chart maps to one standard chart element, multiple combined chart elements, or an Open XML extension chart. |\n| `extension` | no | `string` | Optional Open XML extension namespace or element hint for extension charts. |\n| `series` | no | `array<ref:OpenXmlChartMapping>` | Open XML chart elements used by mixed/composite chart types. |\n| `notes` | no | `string` | Short implementation note for mappings that need renderer interpretation. |\n\n#### ChartDataColumn\n\n- Type: `object`\n- Required fields: `name`, `role`, `type`\n- Purpose: One column of the embedded chart workbook, annotated with its role and grid position.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `name` | yes | `string` | Column header name (e.g. 'Series 1', 'Value', 'Level1', 'Level2'). |\n| `role` | yes | `enum:categoryLabel \\| series \\| helper` | Role this column plays: a category label (axis tick), a series (plotted values), or a helper (calculated/auxiliary). |\n| `type` | yes | `enum:string \\| number` | Cell value type for the column. |\n| `position` | no | `string` | Grid position of the column header in the source workbook, as 'row<N>_col<M>' (zero-indexed). |\n\n#### ChartSampleData\n\n- Type: `object`\n- Required fields: `headers`, `rows`\n- Purpose: Inline sample dataset for previews. Mirrors a small workbook with header row plus data rows.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `headers` | yes | `array<string>` | Header row labels. The first cell typically labels the series column; the rest are category labels. |\n| `rows` | yes | `array<array<string \\| number>>` | Two-dimensional sample data. Each row aligns by index with the headers first cell is the row label, remaining cells are values. |\n\n## Color Scheme\n\n- File: `spec/schemas/color-scheme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-color-scheme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for color-scheme records in the pptx.gallery library. Each scheme is a named palette with the twelve PowerPoint color slots (six accents, two darks, two lights, plus hyperlink and followed-hyperlink), suitable for being mapped directly into OOXML theme XML. Color schemes are referenced from OPF documents via design.colorScheme or design.colorScheme.id; the engine resolves the reference against catalogs.colorSchemes (inline) -> catalogs.colorSchemes.source -> the default catalog at http...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-color-scheme/v1\"` | Identifies this record as a color scheme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this color scheme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable scheme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the palette what mood it evokes and where to use it. |\n| `description` | no | `string` | Longer prose describing the palette and its intended use. |\n| `accent1` | no | `string` | Accent 1 color (hex). Mirrors the OOXML accent1 slot. |\n| `accent2` | no | `string` | Accent 2 color (hex). Mirrors the OOXML accent2 slot. |\n| `accent3` | no | `string` | Accent 3 color (hex). Mirrors the OOXML accent3 slot. |\n| `accent4` | no | `string` | Accent 4 color (hex). Mirrors the OOXML accent4 slot. |\n| `accent5` | no | `string` | Accent 5 color (hex). Mirrors the OOXML accent5 slot. |\n| `accent6` | no | `string` | Accent 6 color (hex). Mirrors the OOXML accent6 slot. |\n| `dark1` | no | `string` | Dark 1 color (hex). Typically the deepest neutral; OOXML dark1. |\n| `dark2` | no | `string` | Dark 2 color (hex). Secondary dark; OOXML dark2. |\n| `light1` | no | `string` | Light 1 color (hex). Typically the slide canvas; OOXML lt1. |\n| `light2` | no | `string` | Light 2 color (hex). Secondary light surface; OOXML lt2. |\n| `hyperlink` | no | `string` | Hyperlink color (hex). OOXML hlink. |\n| `followedHyperlink` | no | `string` | Followed-hyperlink color (hex). OOXML folHlink. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Font Scheme\n\n- File: `spec/schemas/font-scheme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-font-scheme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `major`, `minor`\n- Purpose: Schema for font-scheme records in the pptx.gallery library. Each scheme pairs a major (heading) and minor (body) font family in the OOXML majorFont/minorFont sense, scoped to a target app (PowerPoint or Google Slides) and a language family (Latin, East Asian, or Complex Script). Font schemes are referenced from OPF documents via design.fontScheme or design.fontScheme.id; the engine resolves the reference against catalogs.fontSchemes (inline) catalogs.fontSchemes.source the default catalog at...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-font-scheme/v1\"` | Identifies this record as a font scheme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this font scheme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable scheme name shown in pickers. |\n| `major` | yes | `string` | Heading (major) font family mirrors the OOXML majorFont entry. |\n| `minor` | yes | `string` | Body (minor) font family mirrors the OOXML minorFont entry. |\n| `code` | no | `object` | Optional monospaced font for code blocks and inline code. It has the same shape as the OPF FontScheme 'code' role, so a record and an inline design.fontScheme override are interchangeable. OOXML has no code slot, so e... |\n| `eastAsian` | no | `object` | East Asian script fonts. Maps to the OOXML a:ea element of majorFont (major) and minorFont (minor), and to run-level a:ea. When set, they fill the eastAsian slot for every language; when omitted, the slot comes from t... |\n| `complexScript` | no | `object` | Complex-script fonts (for example Arabic, Hebrew, Indic and Thai). Maps to the OOXML a:cs element of majorFont (major) and minorFont (minor), and to run-level a:cs. When set, they fill the complexScript slot for every... |\n| `type` | no | `enum:sans-serif \\| serif \\| monospace` | High-level typographic class of the scheme. |\n| `app` | no | `enum:PowerPoint \\| Google Slides` | Target application this font pairing is intended for. |\n| `languageFamily` | no | `enum:latin \\| ea \\| cs` | OOXML font-language family this scheme is intended for: 'latin' for Latin-script content, 'ea' for East Asian scripts, 'cs' for Complex Scripts. As the design font scheme, an 'ea' or 'cs' scheme also fills that script... |\n| `languages` | no | `array<string>` | Optional list of human-readable language names this scheme is curated for. Useful for picker UIs that group fonts by language coverage. |\n| `textSample` | no | `string` | Short specimen string used by picker UIs to preview the scheme. |\n| `summary` | no | `string` | One-sentence positioning of the font pairing. |\n| `description` | no | `string` | Longer prose describing the font scheme and where it shines. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Language\n\n- File: `spec/schemas/language.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-language/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `bcp47`\n- Purpose: Schema for language records in the pptx.gallery library. Each record names a presentation language, carries a BCP-47 language tag, and pairs it with sensible default font schemes for PowerPoint and Google Slides output. Languages are referenced from OPF documents via language; the engine resolves the reference against catalogs.languages (inline) catalogs.languages.source the default catalog at https://www.pptx.gallery/languages. The presentation language field also accepts BCP-47 tags directl...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-language/v1\"` | Identifies this record as a language in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this language via language. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable language name. |\n| `code` | no | `string` | ISO 639-3 (or 639-2) three-letter language code. Carried for engines that prefer ISO codes. |\n| `bcp47` | yes | `string` | BCP-47 language tag for this record. Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region form. |\n| `ooxmlLang` | no | `string` | Curated culture tag for OOXML text-run language attributes (a:rPr/@lang, a:endParaRPr/@lang), in the language-[Script-]REGION form Office recognizes (e.g. 'ja-JP', 'ar-SA', 'ms-MY', 'nb-NO', 'fil-PH', 'zh-CN'). Engine... |\n| `direction` | no | `enum:ltr \\| rtl` | Base text direction for the language. When omitted, engines derive it from the script: Arabic (Arab), Hebrew (Hebr), Syriac (Syrc), Thaana (Thaa), N'Ko (Nkoo), Adlam (Adlm), Samaritan (Samr), Mandaic (Mand) and Hanifi... |\n| `script` | no | `string` | ISO 15924 script code of the language's writing system. The script selects the OOXML font slot the language's text uses: East Asian scripts (Hans, Hant, Hani, Jpan, Kore, Hang, Hira, Kana, Bopo, Yiii) use the eastAsia... |\n| `fontScheme` | no | `string` | Default font-scheme id for this language when targeting PowerPoint output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. Its major/minor families fill the language'... |\n| `googleFontScheme` | no | `string` | Default font-scheme id for this language when targeting Google Slides output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. Used in place of 'fontScheme' when resol... |\n| `summary` | no | `string` | One-sentence note about coverage or font defaults. |\n| `description` | no | `string` | Longer prose describing the language record and any font-pairing rationale. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Layout Preview Index\n\n- File: `spec/schemas/layout-preview-index.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-layout-preview-index/v1`\n- Type: `object`\n- Required fields: `$schema`, `version`, `description`, `records`\n- Purpose: Shape of `spec/previews/layouts/index.json`, the manifest for the vendored slide-archetype preview gallery under `spec/previews/layouts/`. Each record names a preview id, its self-contained HTML file, and the file's exact UTF-8 byte length. These preview ids are an archetype taxonomy (e.g. 'swot-analysis', 'org-chart') distinct from the structural layout catalog at spec/catalogs/layouts/ (e.g. 'title', 'chart-2x') see spec/README.md. This schema describes a repo-internal index file, not an OP...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-layout-preview-index/v1\"` | |\n| `version` | yes | `string` | Index format version, as a string. |\n| `description` | yes | `string` | Human-readable description of the preview gallery and its rendering conventions. |\n| `records` | yes | `array<ref:PreviewRecord>` | One entry per vendored preview HTML file. |\n\n### Nested Types\n\n#### PreviewRecord\n\n- Type: `object`\n- Required fields: `id`, `file`, `bytes`\n- Purpose: Summary of one vendored preview HTML file.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Slide-archetype preview id (e.g. 'swot-analysis', 'agenda', 'org-chart'). Does not correspond to a spec/catalogs/layouts/ record id. |\n| `file` | yes | `string` | HTML filename, relative to this index file's directory. |\n| `bytes` | yes | `integer` | Exact UTF-8 byte length of the referenced HTML file's contents. |\n\n## Slide Layout\n\n- File: `spec/schemas/layout.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-layout/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for slide-layout records in the pptx.gallery library. Each record describes a semantic slide layout what regions it exposes and what content kinds those regions are intended to hold. Layouts are referenced from OPF documents via Slide.layout; the engine resolves the reference against catalogs.layouts (inline) catalogs.layouts.source the default catalog at https://www.pptx.gallery/layouts. Free-form custom layout names that don't resolve through any catalog fall through to engine-define...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-layout/v1\"` | Identifies this record as a slide layout in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this layout via Slide.layout. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable layout name shown in layout pickers. |\n| `summary` | no | `string` | One-sentence positioning of the layout when to reach for it. |\n| `description` | no | `string` | Longer prose describing the layout structure and ideal use cases. |\n| `contentType` | no | `enum:Title \\| Text \\| List \\| Image \\| Number \\| Metric \\| Chart \\| Table \\| Code \\| Video \\| Quote \\| Timeline` | Primary kind of content the layout holds. Drives pickers and AI placement decisions. Metric is the canonical numeric/KPI category; Number remains an accepted legacy label. |\n| `contentMultiple` | no | `enum:None \\| 1x \\| 2x \\| 3x \\| 4x \\| 5x \\| 6x` | How many parallel content blocks the layout exposes ('2x' = two-column, '3x' = three-up, etc.). |\n| `contentAlignment` | no | `enum:None \\| Left \\| Center` | Default horizontal alignment of the content area. |\n| `contentBox` | no | `boolean` | Whether the content area is rendered inside a visible box / card. |\n| `contentTypeChartPrimary` | no | `enum:None \\| Top \\| Bottom \\| Left \\| Right` | For chart layouts, where the primary chart sits relative to the rest of the content. |\n| `contentTypeImageFill` | no | `enum:None \\| Crop \\| Fit` | For image layouts, how the image fills its slot. |\n| `contentTypeListBullet` | no | `enum:None \\| Character \\| Image` | For list layouts, how bullets are rendered. |\n| `contentTypeListHeading` | no | `boolean` | For list layouts, whether each list item carries a heading. |\n| `slideTag` | no | `boolean` | Whether the layout includes a small slide-level tag / label region above or near the title. |\n| `slideTitle` | no | `boolean` | Whether the layout includes a slide title region. |\n| `slideSubtitle` | no | `boolean` | Whether the layout includes a slide-level subtitle or supporting-description region. When placeholders is present, this is true exactly when the layout exposes a placeholder with type 'subtitle'. |\n| `slideTitleAlignment` | no | `enum:None \\| Left \\| Center` | Horizontal alignment of the slide title region. |\n| `slideImage` | no | `boolean` | Whether the layout includes a dedicated slide-level image region (separate from any content image). |\n| `slideImageAlignment` | no | `enum:None \\| Top \\| Bottom \\| Left \\| Right \\| Background` | Where the slide-level image sits relative to the content. |\n| `slideLayoutDirection` | no | `enum:None \\| Horizontal \\| Vertical` | Axis along which the layout's primary regions are arranged. |\n| `placeholders` | no | `array<ref:Placeholder>` | Ordered regions the layout exposes. The engine fills 'title', 'subtitle', and 'tag' placeholders from Slide.title, Slide.subtitle, and Slide.tag. Other placeholders are content-kind hints for renderers and pickers. Sl... |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n| `composition` | no | `ref:Composition` | |\n\n### Nested Types\n\n#### Placeholder\n\n- Type: `object`\n- Required fields: `type`\n- Purpose: A single region inside a slide layout. Title, subtitle, and tag placeholders bind to the corresponding Slide fields; other placeholders describe the intended content kind for that region. The array order in the surrounding 'placeholders' field preserves layout region order.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `enum:title \\| subtitle \\| tag \\| text \\| metric \\| quote \\| timeline \\| list \\| chart \\| picture \\| table \\| media \\| diagram \\| code` | OPF placeholder kind. 'text' and 'list' are flexible textual content regions. 'metric' is a numeric/KPI content region filled by a metric payload, including its optional label, description, unit, delta, and trend. The... |\n\n#### Composition\n\n- Type: `object`\n- Required fields: none\n- Purpose: Portable dynamic composition. Slide fields override the resolved layout. Nested groups arrange their children independently, inheriting only minFontSize and overflow. Explicit promoted regions retain their positions.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `mode` | no | `enum:auto \\| grid \\| row \\| column` | auto chooses a grid from available space and content; grid uses columns; row and column use one horizontal or vertical track. |\n| `columns` | no | `integer` | Column count for grid. In auto mode this caps the number of columns. |\n| `gap` | no | `number` | Space between cells as a fraction of the container short edge (canvas at slide root). Default 0.03333333333333333. |\n| `padding` | no | `number` | Inset as a fraction of the container short edge. Default 0.08 on a slide, 0 inside a group. |\n| `weights` | no | `array<number>` | Relative track sizes: columns for row/grid/auto, rows for column. Omitted tracks have weight 1; extra weights are ignored. |\n| `minFontSize` | no | `number` | Minimum readable text size in reference pixels at a 720-pixel canvas short edge. Default 16. Overflow is diagnosed when text cannot fit at this size. |\n| `overflow` | no | `enum:warn \\| error` | warn returns diagnostics for content that does not fit; error rejects layout. Content is never silently removed. Default warn. |\n\n## Narrative Template\n\n- File: `spec/schemas/narrative.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-narrative/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`, `beats`\n- Purpose: Schema for narrative template files in the openpresentation.org catalog. Each template describes a named story arc (e.g. 'problem-solution', 'scqa') as an ordered list of beats. Templates are referenced from OPF documents via narrative either as a bare id string (e.g. 'classic-story') or as an inline object whose shape matches this schema (sans '$schema').\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-narrative/v1\"` | |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this template, e.g. 'problem-solution'. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable template name, e.g. 'Problem Solution'. |\n| `summary` | no | `string` | One-sentence description of when and why to use this narrative. |\n| `description` | no | `string` | Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction. |\n| `audienceFit` | no | `array<string>` | Audiences this narrative works well for, e.g. ['executives', 'investors', 'customers']. |\n| `durationRange` | no | `object` | Typical talk-length window this narrative suits. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search, e.g. ['business', 'pitch', 'internal']. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n| `beats` | yes | `array<ref:Beat>` | Ordered list of beats that make up the narrative arc. |\n\n### Nested Types\n\n#### Beat\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A single narrative beat a labeled segment of the story arc with a specific dramatic purpose. Mirrors the NarrativeBeat definition in opf.schema.json so library entries and inline OPF beats are interchangeable.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable beat name, e.g. 'The Problem'. |\n| `description` | no | `string` | Curator-written prose that explains what this beat should accomplish. |\n| `instructions` | no | `string` | Short author-facing instruction for the beat typically one phrase. Complements 'description' with a concise directive. |\n| `slideCount` | no | `integer` | Optional explicit slide count for this beat. Defaults to 1 when omitted; values >1 are reserved for beats that intentionally span multiple slides. Prefer decomposing a heavy beat into multiple beats over setting a hig... |\n| `slideType` | no | `enum:text \\| list \\| image \\| shape \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Default content kind for the beat's slide. Uses ContentPayload.type names to help engines choose a layout. The legacy shape value is retained for compatibility and requests an image representation; it is not a native... |\n| `layoutHint` | no | `string` | Suggested layout id for the beat's opening slide, e.g. 'section-divider', 'title-slide', 'text-left'. Resolves the same way as Slide.layout against catalogs.layouts and the default catalog at https://www.pptx.gallery/... |\n| `thoughtCues` | no | `array<string>` | Optional speaker or thinking cues attached to the beat. Surfaced in presenter notes. |\n\n## Purpose\n\n- File: `spec/schemas/purpose.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-purpose/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for purpose records in the pptx.gallery library. Each record names a presentation objective such as informing, aligning, persuading, driving a decision, or selling. Purposes are referenced from OPF documents via purpose; the engine resolves the reference against catalogs.purposes (inline) catalogs.purposes.source the default catalog at https://www.pptx.gallery/purposes. The purpose field also accepts free-form strings and inline Purpose objects.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-purpose/v1\"` | Identifies this record as a purpose in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this purpose via purpose. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable purpose name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the purpose what this deck is trying to accomplish. |\n| `description` | no | `string` | Longer prose describing when to use this purpose and how it should shape a deck. |\n| `outcome` | no | `string` | Desired audience outcome after the presentation. |\n| `successCriteria` | no | `array<string>` | Observable signals that the deck accomplished this purpose. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this purpose. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this purpose. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n\n## Social Platform\n\n- File: `spec/schemas/social-platform.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-social-platform/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for social-platform records in the pptx.gallery library. Each record describes a single social-media platform its base URL, profile-URL pattern, handle prefix, brand color, and themed icons. Records are referenced from OPF documents indirectly: the property keys of any Socials object (Organization.socials, Speaker.socials) match record ids, and renderers use the catalog record to format URLs and pick icons. The engine resolves references against catalogs.socialPlatforms (inline) catalo...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-social-platform/v1\"` | Identifies this record as a social-platform entry in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this platform appears as a property key on Socials objects. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable platform name shown in pickers and footers. |\n| `summary` | no | `string` | One-sentence positioning of the platform what it's used for and who's on it. |\n| `description` | no | `string` | Longer prose describing the platform and any rendering conventions (e.g., handle prefixes, distributed instances). |\n| `baseUrl` | no | `string` | Canonical base URL of the platform used as the prefix when normalizing handles to full URLs. |\n| `profileUrlPattern` | no | `string` | URL pattern for individual member profiles. Use '{handle}' as the placeholder for the handle (with the prefix already stripped). |\n| `companyUrlPattern` | no | `string` | Optional URL pattern for organization / company pages, when the platform distinguishes them from member profiles. Use '{handle}' as the placeholder. |\n| `handlePrefix` | no | `string` | Conventional prefix character displayed before the handle (e.g. '@' for X / Mastodon / Threads / TikTok). Empty string when no prefix is used. Renderers strip it before substituting into URL patterns. |\n| `handleExample` | no | `string` | Example handle in its conventional rendered form, used by picker UIs and validation hints. |\n| `brandColor` | no | `string` | Brand color (hex) used for branded icon chips, link styling, or section accents. |\n| `icon` | no | `string` | Default icon source. Accepts an HTTPS URL, data URI, relative path, or asset reference. Used as the fallback when a themed (Light/Dark) variant isn't set. |\n| `iconLight` | no | `string` | Light-colored icon variant intended for rendering on dark backgrounds. |\n| `iconDark` | no | `string` | Dark-colored icon variant intended for rendering on light backgrounds. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n## Theme\n\n- File: `spec/schemas/theme.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-theme/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for theme records in the pptx.gallery library. Each theme is a small, named bundle that pairs a color scheme, a font scheme, a default theme-controlled background, and a slide size. Themes are referenced from OPF documents via design.theme or design.theme.id; the engine resolves the reference against catalogs.themes (inline) catalogs.themes.source the default catalog at https://www.pptx.gallery/themes. Inline overrides on design.colorScheme / design.fontScheme / design.background / des...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-theme/v1\"` | Identifies this record as a theme in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this theme via design.theme. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable theme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the theme when to reach for it. |\n| `description` | no | `string` | Longer prose describing what the theme looks and feels like and the kinds of decks it suits. |\n| `colorScheme` | no | `string` | Catalog reference to the theme's default color scheme resolved against catalogs.colorSchemes the same way design.colorScheme or design.colorScheme.id is. Accepts a bare id, HTTPS URL, or 'pkg:' reference. |\n| `fontScheme` | no | `string` | Catalog reference to the theme's default font scheme resolved against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id is. Accepts a bare id, HTTPS URL, or 'pkg:' reference. |\n| `background` | no | `ref:ThemeBackground` | |\n| `dimensions` | no | `enum:16:9 \\| 4:3 \\| 16:10 \\| letter \\| a4 \\| widescreen \\| standard` | Default slide size for this theme. Accepts the same preset values as design.dimensions.preset. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |\n\n### Nested Types\n\n#### ThemeBackgroundSlot\n\n- Type: `enum:light1 | light2 | dark1 | dark2`\n- Required fields: none\n- Purpose: PowerPoint theme-controlled slide background slot from the active color scheme. These are slots, not assumptions about actual colors: light1 is usually white and dark1 is usually black by convention, but the color scheme controls the real values.\n\n_No named properties._\n\n#### ThemeBackground\n\n- Type: `object`\n- Required fields: `type`, `slot`\n- Purpose: Theme-controlled PowerPoint slide background. The slot is resolved through the active color scheme and remains theme-aware.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"theme\"` | Theme-controlled background fill. |\n| `slot` | yes | `ref:ThemeBackgroundSlot` | |\n\n## Tone\n\n- File: `spec/schemas/tone.schema.json`\n- Schema id: `https://openpresentation.org/schema/opf-tone/v1`\n- Type: `object`\n- Required fields: `$schema`, `id`, `name`\n- Purpose: Schema for tone records in the pptx.gallery library. Each record names a presentation tone (e.g. 'formal', 'casual', 'inspirational') and carries voice cues, anti-patterns, and sample phrases that AI-driven generation uses to shape output. Tones are referenced from OPF documents via tone; the engine resolves the reference against catalogs.tones (inline) catalogs.tones.source the default catalog at https://www.pptx.gallery/tones.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | yes | `const:\"https://openpresentation.org/schema/opf-tone/v1\"` | Identifies this record as a tone in the openpresentation.org catalog. |\n| `id` | yes | `string` | Stable slug used by OPF documents to reference this tone via tone. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable tone name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the tone when to reach for it. |\n| `description` | no | `string` | Longer prose describing the tone and the kinds of decks it suits. |\n| `voiceCues` | no | `array<string>` | Short directives that shape AI generation toward this tone. Phrased as imperatives, e.g. 'use second-person', 'favor short sentences', 'lead with the recommendation'. |\n| `avoid` | no | `array<string>` | Anti-patterns that AI generation should not produce when this tone is active. |\n| `samplePhrases` | no | `array<string>` | Short example phrases that exemplify this tone. Used by picker UIs and as few-shot examples for AI generation. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids this tone pairs well with. Used by picker UIs to suggest narratives once a tone is chosen. Validators warn on unknown ids; never error. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional. |\n"
14
+ },
15
+ {
16
+ "slug": "compatibility-matrix",
17
+ "file": "docs/compatibility-matrix.md",
18
+ "title": "Compatibility matrix",
19
+ "markdown": "# Compatibility matrix\n\nPublished registry evidence for the coordinated Node 24 toolchain. This matrix\nis the honest supported subset for [the developer quickstart](quickstart.md).\nIt is not universal Office parity and does not describe archived prototypes as\nshipped.\n\nVerify live versions with `npm view <package> version` before treating a\ndated handoff as current. The pin set below matches the 21 September 2026 published verification\ncheckpoint in `release-plan.json`. Immutable tag commits pin\nthe verification harnesses; see [published evidence](evidence/shipped-train-20260921/README.md).\nThe [September 29 source checkpoint](handoff-runtime-2026-09-29.md) records later\naccepted fixes and release prerequisites. Those source changes have not updated\nthe versions below or established complete native compatibility.\n\n## Runtime\n\n| Requirement | Status |\n| --- | --- |\n| Node.js | **24.x** on every package below (`engines.node`) |\n| Package managers | npm for published installs; this repo uses pnpm 10.33.2 for core development |\n| Account / model / hosted API | Not required |\n| Operating systems | macOS, Linux, Windows for Node APIs; browser entrypoints are separate |\n\n## Coordinated published packages\n\n| Package | Version | Depends on |\n| --- | --- | --- |\n| `@openpresentation/opf` | 0.11.0 | \u2014 |\n| `@openpresentation/cli` | 0.9.0 | Bundles core 0.11.0; registry metadata has no runtime `dependencies` |\n| `@openpresentation/opf-render` | 0.9.0 | `@openpresentation/opf@^0.11.0` |\n| `@openpresentation/opf-editor` | 0.8.0 | `@openpresentation/opf@^0.11.0`; optional peer `@openpresentation/opf-render@^0.9.0` |\n| `@openpresentation/opf-pptx` | 0.9.1 | `@openpresentation/opf@^0.11.0`; optional peer `@openpresentation/opf-render@^0.9.0` |\n\nInstall the complete pinned set. A caret range starting at 0.10.1 does not\ninclude 0.11.0; old consumers can install a second core and do not establish\nColorRef preview/export support. PPTX 0.9.1 corrects its renderer peer to 0.9.x.\n\nShared header/footer geometry (`furniture-flow-v2`) is published. PPTX exports\neditable slide shapes tagged `OPF_FURNITURE_V1` with provenance for controlled\nreimport. These are not native Office Header/Footer objects (`p:hf` / notes\nmaster). Native Header/Footer work remains [issue 87](https://github.com/OpenPresentation/opf/issues/87).\n\nThe [Windows native-picture checkpoint](evidence/windows-native-picture-20260921/README.md)\nand accepted [native B/C bundle](evidence/windows-native-edits-20260921/README.md)\nrecord finite picture/furniture edits, current-content provenance reimport and\nsafe fallback, production notes packaging and two controlled reordered-file\nrefusals. Core105 publishes that evidence; PPTX47 adds the tested harness, with\nno new package version. UI image replacement changes geometry, longer header\ntext clips, and duplicated tagged headers overlap. Refused workers retain their\nfailed cleanup state separately from later empty-workspace observations.\nThis is not general native layout/reflow fidelity.\n\nAccepted core106's [tab and font checkpoint](evidence/windows-native-tabs-fonts-20260921/README.md)\nrecords plain native tab target error **0.022655487060546875pt** and tab/literal\ndifference **0.022678375244140625pt**, both above the unchanged **0.02pt** gate.\nIts bounded four-face Carlito edit/save/reopen control passes exact text/style\npersistence, zero observed bounds drift, matching rasters and owned font cleanup.\nMixed-size table fidelity, physical glyph-font identity, fallback/synthesis and actual embedding remain\nopen; embedding was disabled for this control. The Windows supervisor retains sole\nOffice control. This documentation task reads evidence and makes no Office calls.\n\nThe later [read-only font inventory](evidence/windows-native-font-inventory-20260921/README.md)\nretains the four Carlito text styles but reports both Carlito and unexpected\nAptos in `Presentation.Fonts`. The native font allowlist fails, and no embedding\nwas attempted. The original parent report incorrectly fails cleanup because of\na Windows PowerShell 5.1 JSON-array parsing defect; raw stages and registration\nrows establish one owned close and four removals in a separately labeled offline\naudit. The raw failure remains intact. Collection names and flags do not identify\nthe physical font used for each glyph.\n\nThe [read-only mixed-table observation](evidence/windows-native-mixed-table-20260921/README.md)\nretains all 245 characters, one literal tab and five authored runs, with outer\ngeometry within 0.02pt and confirmed owned close/font cleanup. Native soft-line\nboundaries are 92/194 versus the estimated preview's 78/172, and native default\ntab spacing is 72pt. These finite content/style results do not pass table\nedit/save/reopen, browser/native raster agreement or physical glyph identity.\nThe accepted [nine-pair offline tab analysis](evidence/windows-native-tab-analysis-20260921/REPORT.md)\nfinds a 0.05pt-compatible pattern in the observed character starts, with finer saved\ntab coordinates. These inputs do not distinguish relative versus absolute placement\nor establish an internal engine cause. The 0.02pt native tab gate remains failed;\nthe separate 0.1px renderer gate is unchanged. Accepted core108 `9b277e1` and\ncore109 `b2711549` publish bounded evidence only. Windows-owned [core110](https://github.com/OpenPresentation/opf/pull/110)\nis merged as `4f7a4bd494f1a873319eff897423d301d1cfc9d6`, from reviewed fc3c36e\nwith four required PR checks passing. [Renderer30](https://github.com/OpenPresentation/opf-render/pull/30) is now merged\nas `c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b`, with exact-head CI 35661051100\npassing. The supervisor reports postmerge 35661504472 also passed. Its companion\nsource preserves rich-tab advances/spans; the [accepted Windows wrap-up](handoff-windows-native-2026-09-21-wrap-up.md)\nrecords bounded source-linked rendering/browser checks and the original missing-test\nCI failure. These checks do not update the frozen registry consumer. Core111\n`3c5048522714365a41d9b5b9ba81620affae718b` publishes the font-inventory evidence\nabove with both PR workflows green; its postmerge workflows were started at the\nfinal notice, not recorded as passed. Package/lock/release/site pins, schema,\ngoldens and tolerances are unchanged. Native allowlist and physical-glyph/embedding\nacceptance remain open; no new package train or broad native pass is inferred.\n\n## Supported in this set\n\n| Capability | How | Notes |\n| --- | --- | --- |\n| JSON authoring | `*.opf.json` plus CLI `opf create` | Local files only |\n| Bundled examples catalog | `@openpresentation/opf/examples` | **126** decks; the quickstart JSON is a docs fixture, not a 127th catalog entry |\n| Validate | `validatePresentation` / `opf validate` | Schema and semantic checks |\n| Color references | `ColorRef`, `variables`, `resolveColorRef` | Core schema/resolution, renderer preview and PPTX resolved colors are shipped. Native `schemeClr`/theme writing and editor canvas named-color fidelity remain follow-ups. |\n| Offline catalog bundle | `bundlePresentation` / `opf bundle` | Inlines resolved catalog records; remote media/data and custom catalog sources remain explicit host concerns. |\n| Lint | `lintSource` / `opf lint` | Read-only; no network catalog fetch |\n| Offline fonts | `prepareNodeFonts` (`/fonts-node`) | Bundled Roboto pack; hashed files |\n| Composition | `composeSlide` | Includes shared headers/footers |\n| Pagination | `paginatePresentation` / `opf paginate` | Returns mappings; preserves source |\n| Edit + undo | `@openpresentation/opf-editor` `createEditorSession` | JSON Patch undo/redo |\n| JSON Patch CLI | `opf edit` | No persistent CLI undo history |\n| SVG preview | `renderSvg` / `renderSvgDeck` | Local; same options as layout |\n| PNG | `svgToPng` | Node raster of SVG |\n| PDF | `svgToPdf` | **Raster-backed**, not selectable text |\n| Editable PPTX export | `toPptx` | OPF \u2192 PPTX serialization. Furniture is tagged slide shapes (`OPF_FURNITURE_V1`), not native `p:hf` / notes-master Header/Footer objects |\n| Agent skills | `opf skills install` | Offline after the CLI is installed |\n| Browser canvas | `@openpresentation/opf-editor/canvas` | Host must supply font bytes |\n\n## Public sites\n\nThe current source, CI and canonical production results are recorded in the\n[current font-readiness checkpoint](evidence/font-readiness-acceptance-20260921/README.md),\n[prior Inspector actions checkpoint](evidence/inspector-current-actions-20260921/README.md),\n[earlier publication checkpoint](evidence/inspector-share-acceptance-20260921/README.md),\n[source-preservation checkpoint](evidence/author-source-acceptance-20260921/README.md),\n[completion checkpoint](evidence/completion-acceptance-20260921/README.md),\n[earlier acceptance ledger](evidence/issue88-final-20260921/README.md) and\n[handoff](handoff-2026-09-21.md). [Issue88](https://github.com/OpenPresentation/opf/issues/88)\nremains open. Package adoption, deployed features and complete workflow\nacceptance are separate claims.\n\n| Surface | Deployed scope and acceptance | Source commit |\n| --- | --- | --- |\n| [openpresentation.org](https://www.openpresentation.org) | Current published guides, agent skills, JSON/preview workflow and downloads. Exact canonical deployment passes 321 checks across 11 pages and 18 raw resources, plus two browser flows for agent installation/navigation and JSON/SVG/PPTX downloads. Reviewed screenshots and output hashes match the accepted build. | `a85bcc77d899ce9ba1df659548be564142c16120` |\n| [pptx.dev](https://www.pptx.dev) `/inspector` and `/author` | App54 merged/live on exact READY production. Premerge Linux/Windows pass 704 units, 13 standalone controls and 39/39 browsers. Full canonical acceptance **fails (34/39 passed)** at five no-POST assertions; the bounded audit does not establish an introduced upload regression. Postmerge Linux 39/39 passes, Windows 38/39 fails initial font readiness. Preset Undo all and broader source writers remain unresolved. | `8f54228a9e38a1dcc0bf8188bcdd519795b3799a` |\n| [pptx.gallery](https://www.pptx.gallery) `/docs`, `/editor` and gallery pages | Published ColorRef/bundle guidance, Playground and Editor actions, and the canonical docs-to-editor flow are verified. | `f17e9ae5869669d5fbac3720f285652d0c37551c` |\n\nThe site uses documentation source `120a770`, whose tree matches accepted core\nPR98 commit `b1ff81db6f8714b0db1a98bde482ed8a64d0ccc9`. Core PR93/97/98/99/100/102/103 passed\npre-merge and post-merge CI. Accepted core102 is\n`578bcc6e0894129b00059258bd4ad1994a414baa`; its reviewed and accepted trees match,\nand all four required pre/post-merge runs passed on their first attempts. The\n[core102 receipts](evidence/inspector-share-acceptance-20260921/README.md#accepted-core102)\npin this documentation checkpoint without changing the site's older accepted\ndocumentation snapshot. The site's complete guides and raw resources match\nthe reviewed source; binary evidence remains linked and downloadable without\nbeing decoded into the AI-facing guide.\nCore104 `3d301f1` preserves exact postmerge OPF success and coordinated cancellation;\nit is not a complete green postmerge gate. Accepted descendant core105\n`84e914710520a7b0e777fce30e5758ee64a64924` preserves all 60 core104 evidence blobs\nand passes both exact-head workflows on their first attempts. [Compact receipts](evidence/inspector-current-actions-20260921/README.md#core-source-and-ci)\nkeep descendant acceptance separate from the canceled predecessor run.\nAccepted core106 `3847f712ccb2379952bcc8ab7c9fdbaedfd0a4ce` also passes both\npre/postmerge workflows on their first attempts. Its [compact receipt](evidence/inspector-current-actions-20260921/core106/acceptance-receipt.json)\nbinds the bounded native evidence above without completing general compatibility.\nAccepted core107 `5bc0d3f89414b382b2ce48452c7e56e5d66aaf74` has reviewed tree\n`ffdc678beadf0808bc717d67e7fc0a9ec4790127`. Both original postmerge push workflows\n**35652360502 / 35652360551** passed on attempt 1 under Node24.20.0.\n[Compact receipts](evidence/font-readiness-acceptance-20260921/README.md#core107-acceptance)\nretain earlier automatic premerge cancellations separately from the later automatic\nsuccessful pair; no rerun or accepted checkpoint relabels them.\n\n[App47](https://github.com/Data-Advantage/pptx-dev/pull/47) corrects the pre-app47\ncompletion adapter's rejected layout choices while preserving unchanged source\ntokens and undo history. Accepted commit `0f35352a1445f56ad4bb7c9f4c5609e01f2dd9ae`\nhas reviewed tree `203bdab509d05911f04f234d996f9c91f2b5e4f2`, green Linux/Windows\npre/post-merge CI and its exact READY canonical deployment. The historical App47 **23/24** production run passes all five new completion cases but still fails the existing\nLF Author third-popup assertion. This does not establish complete public-surface\nacceptance; the [historical App47 report](evidence/completion-acceptance-20260921/canonical/REPORT.md)\nand [earlier failed app45/app46 results](evidence/issue88-final-20260921/README.md)\nretain their evidence and unresolved causes.\n\nThe [source audit](evidence/completion-acceptance-20260921/source-preservation-audit/REPORT.md)\nidentified Author canvas/Copy/export and Inspector JSON-download normalization.\nMerged [App53](https://github.com/Data-Advantage/pptx-dev/pull/53) at\n`e40c287b64fcbcfb85fb4a8a50641aea8e3e54a8` has the identical reviewed b33dc18\ntree and preserves those bounded raw-source\npaths and corrects order-only reimport history. A public Suggest-action guard\naddresses the observed stale Quick Input context competing with focused-editor\nCtrl+Space. Current local checks pass **627 unit tests and 29/29 browser cases\nin 88.78 seconds**, zero retries. First-attempt Linux/Windows application CI\npassed 627 unit tests and 29 browser cases per platform; artifact CI also passed.\nThe [exact READY canonical run](evidence/author-source-acceptance-20260921/canonical/REPORT.md) passes **29/29**, zero retries, with matching deployment receipts before and after. Postmerge application CI fails Linux **28/29** while Windows passes **29/29**; both pass 627 unit tests and separate artifact CI passes. The [Linux failure](evidence/author-source-acceptance-20260921/app53/postmerge-ci/README.md) stops before security assertions because five default-deck canvases remain after the shared-load toast. No rerun or canonical pass replaces that failed gate. The retained draft preview was READY but not browser-accepted.\n\nThe earlier **24/27** import-undo regression, **26/27** local popup failure and\nold-head Windows **26/27** shared-load failure remain historical evidence.\nThe fresh successful Windows job retained its sanitized timing artifact, but\nonly Author timings survived; the Inspector pagehide snapshot is missing. This\ndoes not explain or fix the old readiness delay. Phase4\ncaptured no post-fix stale-context overlap, so causal stress is inconclusive.\nThe existing suggestion-details pane remains clipped; visibility is not legibility.\nThe [postmerge trace diagnosis](evidence/author-source-acceptance-20260921/inspector-share-diagnosis/REPORT.md)\nproves wrong-document automatic share-hash publication during import.\n[App54](https://github.com/Data-Advantage/pptx-dev/pull/54) first corrected automatic\npublication at `60c91f6`: authoritative source/format is checked before and after\nencoding, load/navigation guards remain, and obsolete `import=hash:` is removed.\nURL transfer preserves semantics, not raw spelling. Local 659-unit/31-browser\nacceptance does not replace original first-attempt CI **35638158483**, which\npassed Linux **31/31** but failed Windows **30/31** at the unchanged 45-second\nAuthor readiness deadline. Both publication cases passed. The [frozen diagnosis](evidence/inspector-share-acceptance-20260921/app54/windows-timeout/REPORT.md)\nretains bounded slow-delivery observations with unknown cause. Late assertions\nare not an in-budget pass. Browser History tests permit prior accepted content\nuntil first new publication; held-promise controls establish the narrower race guard.\n\nThe product correction was added at `57e5e59fddbc94346f142dc12d86a916228bf2ae`, tree\n`d9c3aab543c6a886a85c6ec07dd55e5ab22590cd`. It guards current snapshots for\nexplicit Copy/JSON/Share/PPTX/PDF/Author/Deckchat actions. Pending public canvas\ndrafts commit before capture, pointer-blur rejection survives session recovery,\nand newer source/load/navigation/unmount/action invalidates late results. Raw\nCopy and accepted same-format JSON bytes are preserved; handoffs remain semantic.\nAlready-started clipboard/download effects cannot be retroactively canceled.\nThe integrated portable standalone startup verifies packaged runtime assets,\nfonts and traced schema inputs without changing dependencies or published pins.\n\nLocal runtime checks pass **692 unit tests**, **7 standalone controls**, focused\n**8/8** and full **39/39** browser cases, zero retries, with visual review. The\n[immutable 93-file app bundle](https://github.com/Data-Advantage/pptx-dev/tree/57e5e59fddbc94346f142dc12d86a916228bf2ae/docs/evidence/inspector-action-snapshots-20260921)\nretains stale-draft negative evidence and the initial candidate **7/8** result.\nThe latter did not establish accepted pasted source before releasing mocked PDF 401;\nfinal visible-code preconditions change no product bytes or budgets. PDF/Deckchat\nare locally mocked. CRLF ingress normalized 279 to 274 LF bytes; subsequent exact\nactions preserve the accepted buffer, not that ingress boundary.\n\n**The original 57e packaging gate failed.** First-attempt CI **35645493900** failed Linux and\nWindows typecheck on archived `.spec.ts` evidence copies after each platform\npassed 692 units and 7 standalone controls. The exact-head preview failed with\n`module_not_found`; precise Vercel compiler logs were unavailable. [The first-attempt receipt](evidence/inspector-current-actions-20260921/app54/first57-ci/REPORT.md)\nrecords skipped build/browser steps and no uploaded artifacts. The passing runtime build\npredates those copies. A byte-identical `.ts.txt` archive correction produced captured App54 head\n`6dfc2584698c1306505929a2bc3e427996d66563`, tree\n`f3279495f7685945b7541c90d24f7239407639a0`. Its final-tree local typecheck passes\nwith all 305 product/test inputs unchanged. [The corrected receipt](evidence/inspector-current-actions-20260921/app54/corrected6df/commit-receipt.json)\nbinds its 106-file evidence bundle. [Exact-head CI 35646213757](evidence/inspector-current-actions-20260921/app54/corrected6df/ci/REPORT.md)\npasses 692 units, seven standalone controls, typechecks and build on both platforms;\nLinux passes **39/39** browsers. Windows executes **zero browser tests** because\nstandalone startup fails with `EPERM` while statting its packaged React dependency\nlink. No timing or test-result artifacts were uploaded. The exact-head preview\nis READY, which is metadata only; at that 6df checkpoint App54 was unmerged and undeployed to production.\nThe [current ledger](evidence/inspector-current-actions-20260921/README.md)\nretains the separate 60c readiness, 57e packaging and 6df startup failures. Production then remained App53 `e40c287`, with its original\nfailed Linux postmerge gate preserved separately from canonical **29/29**.\nThe [startup-link correction at 4338e57](https://github.com/Data-Advantage/pptx-dev/blob/4338e57b951c469d5c5f239b78a303fbad3c745e/docs/evidence/standalone-windows-links-20260921/README.md)\nthen reached all browser cases: Linux **39/39**, Windows **38/39** in first-attempt\nCI **35649707689**, with 692 units and 13 standalone controls passing per platform.\nThe sole Windows failure was initial canvas-title visibility at five seconds,\nbefore any edit/recovery operation; correct incoming source remained at loading\nfonts. Late font acquisition and eleven incomplete responses at teardown do not\nestablish a permanent stall or a dominant cause. The [failed gate](evidence/font-readiness-acceptance-20260921/README.md#preserved-failed-windows-gate)\nis preserved.\n\nApp54 font-preparation revision `a4eb88ab7aa585c9efb91de4c190c1f1c0c7d0eb`, tree\n`031d893855a540fd2a4d2ec2162605817b8dde16`, overlaps font acquisition with converter\nwarmup while readiness still waits for both. The offline converter barrier and all\n**33 faces / 9,317,044 bytes**, manifest, substitution/measurement policy and\n`document.fonts.ready` gate remain unchanged. Fresh local Node24.21.0 checks pass\n**704 units, 13 standalone controls and 39/39 browsers**, zero retries, including\nunchanged offline export/reimport. [Immutable app evidence](https://github.com/Data-Advantage/pptx-dev/tree/a4eb88ab7aa585c9efb91de4c190c1f1c0c7d0eb/docs/evidence/font-preparation-concurrency-20260921)\nretains reviewed images, exact source/output bindings and the original failed run.\n\n**The original a4eb gate failed:** first-attempt application **35654753237** passes\nLinux **39/39** but fails Windows **38/39**; both pass 704 units, 13 standalone\ncontrols, typecheck and build. The Open in Author URL assertion exceeds its existing\nfive-second deadline at `inspector-actions.spec.ts:176`; later source checks are\nnot reached. Original4338 font readiness passes in this run. No navigation cause\nor data-loss finding is established. The [frozen final audit](evidence/font-readiness-acceptance-20260921/app54/final-a4eb-ci/release-audit.json)\nretains exact source/artifact bindings and the original failed trace; that failure remains preserved.\nThe Python artifact workflow is not applicable under its full-PR path filters,\nnot a fresh pass. Exact-head preview is READY metadata only; an\nunauthenticated request redirects to sign-in, with no preview-browser acceptance.\nAt that a4eb capture App54 was unmerged and production remained App53\n`e40c287`. The separate suggestion-details candidate remains unreleased and supplies\nno acceptance here. No local/browser pass broadens native compatibility.\nThe [bounded navigation diagnosis](evidence/font-readiness-acceptance-20260921/README.md#author-navigation-diagnosis-and-prospective-policy)\nrecords Loading Author and a delayed successful script response: 1490 bytes inferred\nfrom ETag, 766 compressed bytes recorded, actual body absent. Later DOM does not\naccept unreached assertions or identify a cause. App54 accepted merge\n`8f54228a9e38a1dcc0bf8188bcdd519795b3799a` retains reviewed 79ba tree\n`3639d3c14daec94d13711fa2b10f2b927df45eca`, preregisters `waitForURL(load)` before\nthe real action, matching `page.goto` within the unchanged 45-second test and default\nfive-second content budgets. It retains all oracles but deliberately removes the\nincidental five-second navigation deadline. Fresh local **39/39**, zero retries,\npasses in 112.780048s with reviewed source/images and prepared-tree build/typecheck.\nThe [immutable app bundle](https://github.com/Data-Advantage/pptx-dev/tree/79ba0157984fce8405eeb786b8ede1a4e59ba138/docs/evidence/author-navigation-policy-20260921)\nretains original failures. Units/standalone controls were not repeated locally;\nfresh first-attempt application **35659187971 passes 704 units, 13 standalone\ncontrols and 39/39 browsers on both Linux and Windows**. Exact-head preview was\nREADY/protected, not browser accepted. The identical reviewed tree is merged/live\nat 8f on READY deployment `dpl_H1FtXx1QSuGxn3MwzJwWJpGtRg8b`, but full canonical\nacceptance **fails (34/39 passed)** at five no-POST assertions observing Clerk environment\nPOSTs. The [safe audit](evidence/font-readiness-acceptance-20260921/app54/canonical8f/write-audit/REPORT.md.txt)\nrecords ten such requests, nine with HTTP200/zero-length bodies and one incomplete.\nNo fixture-content needle was detected in captured fields; uncaptured data remains\nunknown. Three final action page-error assertions were not reached; the two share\ncases passed theirs. The prior auth/config comparison is 28/29 identical, with only\npackage scripts changed. Production is kept without a rollback, test change or\nrerun; the strict gate remains failed. Raw authentication-bearing diagnostics\nremain private. [Postmerge CI 35660464578](https://github.com/Data-Advantage/pptx-dev/actions/runs/35660464578)\nfinishes failed: Linux 39/39 passes while Windows 38/39 fails, with 704 units/13 controls/typecheck/build\npassing on each. Windows fails initial gallery-rail title visibility after 5,000ms\nwith correct source, clean schema and Loading slide fonts; later editing/export/\nreimport checks were not reached. The [final audit](evidence/font-readiness-acceptance-20260921/app54/merged8f/postmerge-ci/REPORT.md.txt)\npreserves this separate failed gate without cause inference or a retry. No canonical\npass or general native/font acceptance is claimed. That September 21 checkpoint was paused; the user resumed work on September 29. See the [current source checkpoint](handoff-runtime-2026-09-29.md) for ongoing repairs and release holds.\n\nSeparate local negative controls confirmed that preset Undo all discarded New run\nand imported replacement documents. The guarded correction is now preserved in\n[draft app #58](https://github.com/Data-Advantage/pptx-dev/pull/58): independent\nreview and local Node 24 checks passed (716 units, 13 standalone controls and\n49 browsers without retries). Original Linux/Windows CI could not start because\nof the account payment/spending-limit restriction; no application CI or production\nacceptance is claimed. Unbusy asynchronous account replacements still need a\nsynchronous invalidation guard and held-response control.\nOther local source writers still require their separate preservation checks. These unresolved local findings and raw imported-file/account/agent/metadata boundaries remain outside\nApp53 and App54. See the [App53 ledger](evidence/author-source-acceptance-20260921/README.md)\nand its immutable application evidence links. Issue88 remains OPEN. Native/font\ncompatibility, required repair and release gates remain separate; geometry is\ndeferred as the coordinated set below. The unimplemented worker candidate remains in the [handoff](handoff-2026-09-21.md).\n\nThe five coordinated geometry drafts (core94, renderer27, editor25, PPTX42,\nsite40) remain unmerged. In particular, site40 is not independently shipped.\n\n## Explicitly not shipped\n\n| Topic | Tracker | Do not describe as done |\n| --- | --- | --- |\n| Linux vs Chromium native-width residual at the 0.1px gate | [opf-render#24](https://github.com/OpenPresentation/opf-render/issues/24) | Rounding that fixes Linux but breaks macOS is rejected |\n| General native PowerPoint fidelity and real Office Header/Footer objects (`p:hf`) | [opf#87](https://github.com/OpenPresentation/opf/issues/87) | Finite B/C and bounded Carlito edit controls above are accepted evidence, as is one finite mixed-size table edit/save/reopen ([evidence](evidence/windows-native-mixed-edit-20260922/README.md)). Tab tolerance, general mixed-size table layout and preview/native wrapping, physical glyph identity/fallback/synthesis, embedding and general layout/reflow fidelity remain open; self-import and tagged furniture do not certify arbitrary Office behavior |\n| Public-surface acceptance checklist | [opf#88](https://github.com/OpenPresentation/opf/issues/88) | Shipping features does not establish every acceptance item; use the checklist and deployment receipts |\n| HarfBuzz / prepared-glyph shaping | Archive branches `codex/archive-shaping-20260915` | Prototypes are preserved, not in npm |\n| Selectable vector PDF | [pdf plan](plans/pdf-export.md) | Follows font reliability |\n| General SVG diagrams / Mermaid | [diagrams plan](plans/diagrams-svg.md) | Embedded SVG \u2260 native editable primitives |\n| Full visual editor / IME / bidi / repair loop | [developer adoption](plans/developer-adoption-20260915.md) | Schema support \u2260 WYSIWYG coverage |\n\nCLI 0.9.0 does not render or export PPTX. Browser `svgToPng` / `svgToPdf` are\nnot available; those are Node APIs.\n\n## Predecessor notes\n\n| Older set | Relationship |\n| --- | --- |\n| core 0.10.0, renderer/PPTX/CLI 0.8.0, editor 0.7.0 | Previous coordinated Node 24 baseline. Lint and furniture landed across 0.10.0/0.8.0 then layout-placeholder fixes in 0.10.1/0.8.1/0.7.1. |\n| Node 20 / 22 | Not valid for these packages |\n\nDo not install sibling `../opf-render` dist folders when following the\nquickstart. Packed and registry consumers must resolve `@openpresentation/*`\nfrom npm.\n"
14
20
  },
15
21
  {
16
22
  "slug": "content-item-design-overrides",
17
23
  "file": "docs/content-item-design-overrides.md",
18
24
  "title": "Possible Content Payload Design",
19
- "markdown": '# Possible Content Payload Design\n\nThis is a parking-lot note for design controls intentionally removed from slide content payloads while the v1 content model stabilizes.\n\nCurrent principle: root slide payload fields and promoted region payloads should describe what the slide contains. Layout and rendering decide how it looks. If per-payload styling returns later, it should live in an explicit override surface rather than mixing presentation controls into the base content payload.\n\n## Possible Shape\n\n```jsonc\n{\n "title": "Revenue",\n "left": {\n "text": "Revenue grew 28%",\n "design": {\n "text": {\n "alignment": "center"\n }\n }\n }\n}\n```\n\nOpen question: whether overrides belong inline on each content payload, in `slides[].design`, or in a reusable style catalog keyed by region key or payload `type`.\n\n## Deferred Fields\n\nThese fields were deliberately kept out of `ContentPayload` for now:\n\n| Area | Candidate fields |\n| --- | --- |\n| Placement | `position`, `size`, `zIndex`, `rotation` |\n| Text | `style`, `fontSize`, `fontFamily`, `color`, `alignment`, `lineHeight`, `textTransform`, `verticalAlignment` |\n| Image/media | `fit`, `borderRadius`, `shadow`, `opacity`, `crop`, `focalPoint` |\n| Chart | `chartPreset`, `options`, `legend`, `showValues`, `showGrid`, `stacked`, `xAxis`, `yAxis`, palette overrides |\n| Table | `tableStyle`, `stripedRows`, `compact`, border colors, header colors |\n| Shape | `fill`, `stroke`, `cornerRadius`, `path` |\n| Code | `theme`, `showLineNumbers`, syntax-highlighting theme |\n\n## Decision Criteria\n\nBring these back only when there is a concrete renderer or authoring workflow that needs them. Prefer small, typed override objects over a large flat bag of visual fields on every content payload.\n'
25
+ "markdown": '# Possible Content Payload Design\n\nThis is a parking-lot note for design controls intentionally removed from slide content payloads while the v1 content model stabilizes.\n\nCurrent principle: root slide payload fields and promoted region payloads should describe what the slide contains. Layout and rendering decide how it looks. If per-payload styling returns later, it should live in an explicit override surface rather than mixing presentation controls into the base content payload.\n\n**Standing requirement for any styling surface that does return:** every color field must accept the shared `ColorRef` forms \u2014 literal hex, a color-scheme slot or role name, and a `var:<id>` variable reference \u2014 from its first release, never hex alone. Rich-text runs and styled table cells already follow this (see [`content-payloads.md`](./content-payloads.md) \u2192 "Color references"); the 0.10 styled-cell surface initially shipped hex-only and had to be widened, which is the failure mode this requirement exists to prevent. A styling surface that ships hex-only freezes every styled deck\'s palette outside the design system and breaks re-theming.\n\n## Possible Shape\n\n```jsonc\n{\n "title": "Revenue",\n "left": {\n "text": "Revenue grew 28%",\n "design": {\n "text": {\n "alignment": "center"\n }\n }\n }\n}\n```\n\nOpen question: whether overrides belong inline on each content payload, in `slides[].design`, or in a reusable style catalog keyed by region key or payload `type`.\n\n## Deferred Fields\n\nThese fields were deliberately kept out of `ContentPayload` for now:\n\n| Area | Candidate fields |\n| --- | --- |\n| Placement | `position`, `size`, `zIndex`, `rotation` |\n| Text | `style`, `fontSize`, `fontFamily`, `color`, `alignment`, `lineHeight`, `textTransform`, `verticalAlignment` |\n| Image/media | `fit`, `borderRadius`, `shadow`, `opacity`, `crop`, `focalPoint` |\n| Chart | `chartPreset`, `options`, `legend`, `showValues`, `showGrid`, `stacked`, `xAxis`, `yAxis`, palette overrides |\n| Table | `tableStyle`, `stripedRows`, `compact`, border colors, header colors |\n| Shape | `fill`, `stroke`, `cornerRadius`, `path` |\n| Code | `theme`, `showLineNumbers`, syntax-highlighting theme |\n\n## Decision Criteria\n\nBring these back only when there is a concrete renderer or authoring workflow that needs them. Prefer small, typed override objects over a large flat bag of visual fields on every content payload.\n'
20
26
  },
21
27
  {
22
28
  "slug": "content-payloads",
23
29
  "file": "docs/content-payloads.md",
24
30
  "title": "Content Payloads",
25
- "markdown": '# Content Payloads\n\nSlide content lives directly on a slide as a full-slide payload, in layout-agnostic `blocks`, or inside a promoted region key such as `left`, `center+right`, or `top:left`.\n\nThe optional payload `type` can make intent explicit, but OPF should usually infer the content kind from the field present:\n\n| Field | Inferred type | Notes |\n| --- | --- | --- |\n| `text` | `text` | Plain string or `TextRun[]`. |\n| `bullets` | `text` | Simple text bullets, usually `string[]`. |\n| `items` | `list` | Generic list payload, usually `string[]` or `ListItem[]`. |\n| `image` | `image` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `video` | `video` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `chart` | `chart` | Chart object with `type` and tabular `data`. |\n| `table` | `table` | Table object with optional `columns` and required `rows`. |\n| `code` | `code` | String shorthand or `Code` object with `source`, `language`, and `filename`. |\n| `metric` | `metric` | String/number shorthand or `Metric` object with `value`, `label`, `description`, `unit`, `delta`, and `trend`. |\n| `quote` | `quote` | String shorthand or `Quote` object with `text`, `attribution`, and `source`. |\n| `timeline` | `timeline` | Array shorthand or `Timeline` object with `name`, `description`, and `events`. |\n\n## Blocks\n\nUse slide-level `blocks` when a slide contains multiple content payloads, but exact placement should be inferred by the renderer. Blocks may contain a concrete content payload or a nested group with its own `blocks` and optional `composition`. Groups cannot mix child blocks with leaf payload fields. See [dynamic composition](dynamic-composition.md) for nesting and inheritance rules.\n\n```json\n{\n "title": "Customer Feedback Summary",\n "blocks": [\n {\n "table": {\n "columns": ["Theme", "Mentions"],\n "rows": [\n ["Speed", 42],\n ["Ease of use", 31]\n ]\n }\n },\n {\n "quote": {\n "text": "The new workflow cut review time in half.",\n "attribution": "Operations Lead",\n "source": "Customer interview"\n }\n }\n ]\n}\n```\n\nAt slide root only, multiple content payload kinds are accepted as shorthand for the equivalent blocks form when there is no explicit `type`, no `blocks`, and no promoted region keys:\n\n```json\n{\n "title": "Habitat & Territory",\n "text": "Jaguars are strongly associated with presence of water and dense cover.",\n "items": [\n "Primary habitats include dense rainforests, swamps, and seasonally flooded wetlands.",\n "Solitary animals that establish and defend large territories."\n ]\n}\n```\n\nThe same shorthand works for other content kinds:\n\n```json\n{\n "title": "Evidence Snapshot",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Sightings"],\n "rows": [\n ["Q1", 12],\n ["Q2", 18]\n ]\n }\n },\n "quote": {\n "text": "Jaguar conservation depends on connected habitat.",\n "attribution": "Field researcher"\n }\n}\n```\n\n## Chart\n\nChart-specific fields are grouped under `chart`. Do not put loose chart data directly on a slide or region.\n\n```json\n{\n "title": "Revenue Trend",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Revenue", "Costs"],\n "rows": [\n ["Q1", 12, 8],\n ["Q2", 18, 11],\n ["Q3", 24, 15]\n ]\n }\n }\n}\n```\n\nInline chart data is tabular by default. Renderers convert `columns` and `rows` into series, axes, legends, and workbook data internally.\n\nAsset-backed data is still table-oriented:\n\n```json\n{\n "chart": {\n "type": "column",\n "data": {\n "src": "asset:revenue-csv",\n "columns": ["Quarter", "Revenue"]\n }\n }\n}\n```\n\n## Table\n\nTable-specific fields are grouped under `table`. Do not put loose `columns` or `rows` directly on a slide or region.\n\n```json\n{\n "title": "Pipeline",\n "table": {\n "columns": ["Stage", "Count", "Value"],\n "rows": [\n ["Qualified", 42, "$1.2M"],\n ["Proposal", 18, "$840K"]\n ]\n }\n}\n```\n\nTable body cells accept strings, numbers, booleans, or `null`. Since core 0.5.0, a cell or column header also accepts the same `TextRun[]` used by rich text:\n\n```json\n{\n "table": {\n "columns": [["Quarter ", {"text": "growth", "bold": true}], "Value"],\n "rows": [\n [["Up ", {"text": "12%", "color": "#008800"}], 12]\n ]\n }\n}\n```\n\nUse core 0.6.0, renderer 0.4.0, editor 0.3.0 and PPTX 0.4.0 together. Core measures run styles when checking overflow and keeps each row intact when paginating. The renderer traces rich cells for the editor\'s existing formatting, typing and undo controls; the exporter emits editable native text runs. PPTX 0.4.0 imports supported native character styles, paragraph defaults, theme fonts/colors, external links and significant whitespace as rich runs. Unstyled body cells remain strings, and cached display text cannot recover original scalar types or live fields. Conditional table styles, merged geometry and cell fills/borders/alignment remain limited; native PowerPoint visual parity is not yet verified.\n\nCore 0.6.0 adds `layoutTable` from `@openpresentation/opf/composition`. It measures scalar and rich cells, keeps short rows compact, and gives wrapped or multiline rows the height they need. When space is constrained it reduces spare row height before shrinking text, and reports overflow when the minimum fitting size cannot fit. Pass the same `scale`, font family, measurement provider and effective `minFontSize` to each consumer. The returned row boxes, cell text boxes and fits are shared by the coordinated SVG and PPTX implementations; rich table cells use uniform line advances to match native cell paragraph spacing. Native viewer fidelity remains a separate verification boundary.\n\n\n## Code\n\nCode-specific fields are grouped under `code`. A string value is shorthand for `code.source`; use object form when syntax highlighting or a file label matters. In object form, `source` is required.\n\n```json\n{\n "title": "Decision Rule",\n "code": {\n "source": "if risk > threshold:\\n escalate(owner)\\nelse:\\n approve(change)",\n "language": "python",\n "filename": "decision.py"\n }\n}\n```\n\n## Metric\n\nMetric-specific fields are grouped under `metric`. A string or number value is shorthand for `metric.value`; numeric values stay numeric and are formatted by renderers at display time. Use object form when labels, descriptions, units, deltas, or trends matter.\n\nThe `number-1x` through `number-6x` layout IDs declare one title placeholder and one through six `metric` placeholders. The IDs retain their existing names; the content kind and payload key are `metric`, not `number` or `text`. For several metrics, use separate `{ "metric": ... }` entries in `blocks`. Choosing a layout does not reinterpret existing text as numeric data.\n\n```json\n{\n "title": "Operating Metric",\n "metric": {\n "value": "42%",\n "label": "Review cycle reduction",\n "description": "Median reduction across customer review workflows.",\n "delta": "+11 pts",\n "trend": "up"\n }\n}\n```\n\n## Quote\n\nQuote-specific fields are grouped under `quote`. A string value is shorthand for `quote.text`; use object form when attribution or citation matters.\n\n```json\n{\n "title": "Customer Proof",\n "quote": {\n "text": "The new workflow made exceptions visible before they became escalations.",\n "attribution": "VP Operations, Acme Corp",\n "source": "Customer interview"\n }\n}\n```\n\n## Timeline\n\nTimeline-specific fields are grouped under `timeline`. An array value is shorthand for `timeline.events`; use object form when the timeline needs a name or description. Timeline events use `when`, `what`, and `description`.\n\n```json\n{\n "title": "Rollout Plan",\n "timeline": {\n "name": "Regional Rollout",\n "description": "Major milestones for the rollout.",\n "events": [\n {\n "when": "Q1",\n "what": "Pilot",\n "description": "Launch with one operations team."\n },\n {\n "when": "Q2",\n "what": "Rollout",\n "description": "Expand to all regions."\n }\n ]\n }\n}\n```\n\n## Regions\n\nRegion keys address a 3\xD73 grid of rows (`top`, `middle`, `bottom`) and columns (`left`, `center`, `right`):\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n```\n\n- A bare column key (`left`) spans all three rows; a bare row key (`top`) spans all three columns.\n- `+` spans adjacent rows or columns: `center+right`, `top+middle`.\n- `row:column` combines the two: `top:left`, `middle+bottom:center+right`.\n- Keys on one slide must not overlap, and regions cannot be mixed with root payload fields.\n\nSpans compose into common slide shapes:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThe same payload objects work inside regions \u2014 here, the sidebar-plus-main shape:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": {\n "table": {\n "columns": ["Metric", "Value"],\n "rows": [\n ["Revenue", "$4.2M"],\n ["Gross margin", "68%"]\n ]\n }\n },\n "center+right": {\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Month", "Revenue"],\n "rows": [\n ["Jan", 3.4],\n ["Feb", 3.8],\n ["Mar", 4.2]\n ]\n }\n }\n }\n}\n```\n'
31
+ "markdown": '# Content Payloads\n\nSlide content lives directly on a slide as a full-slide payload, in layout-agnostic `blocks`, or inside a promoted region key such as `left`, `center+right`, or `top:left`.\n\nThe optional payload `type` can make intent explicit, but OPF should usually infer the content kind from the field present:\n\n| Field | Inferred type | Notes |\n| --- | --- | --- |\n| `text` | `text` | Plain string or `TextRun[]`. |\n| `bullets` | `text` | Simple text bullets, usually `string[]`. |\n| `items` | `list` | Generic list payload, usually `string[]` or `ListItem[]`. |\n| `image` | `image` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `video` | `video` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `chart` | `chart` | Chart object with `type` and tabular `data`. |\n| `table` | `table` | Table object with optional `columns` and required `rows`. |\n| `code` | `code` | String shorthand or `Code` object with `source`, `language`, and `filename`. |\n| `metric` | `metric` | String/number shorthand or `Metric` object with `value`, `label`, `description`, `unit`, `delta`, and `trend`. |\n| `quote` | `quote` | String shorthand or `Quote` object with `text`, `attribution`, and `source`. |\n| `timeline` | `timeline` | Array shorthand or `Timeline` object with `name`, `description`, and `events`. |\n\n## Color references\n\nEvery content color field \u2014 `TextRun.color`, styled table cell `style.fill` and `style.color`, and table cell border `color` \u2014 accepts three forms:\n\n- A literal hex color: `"#0F172A"`, `"#B42318CC"`.\n- A color-scheme slot or role name, resolved through the effective color scheme after design resolution: slots `accent1`\u2013`accent6`, `dark1`, `dark2`, `light1`, `light2`, `hyperlink`, `followedHyperlink`; roles `primary`, `secondary`, `accent`, `background`, `surface`, `text`, `textSecondary`.\n- A variable reference `var:<id>` into the top-level `variables` map.\n\n```json\n{\n "variables": { "risk": "#B42318" },\n "slides": [\n {\n "title": "What Could Go Wrong",\n "items": [\n ["Two regions at ", { "text": "85% utilization", "color": "var:risk", "bold": true }],\n ["Mitigations ship in ", { "text": "November", "color": "accent2" }]\n ]\n }\n ]\n}\n```\n\nPrefer names and variables over literal hex: re-theming the deck updates every named reference, while a hex value stays frozen at authoring time. An unknown `var:` id is a validation warning, never an error; engines fall back to their default text color. The styled table cell and border color fields enforce the three forms at the schema level; run colors additionally accept any string so imported decks keep validating \u2014 unrecognized values warn, and renderers fall back to the theme color. See [`design-resolution.md`](./design-resolution.md) for the resolution rules.\n\n## Blocks\n\nUse slide-level `blocks` when a slide contains multiple content payloads, but exact placement should be inferred by the renderer. Blocks may contain a concrete content payload or a nested group with its own `blocks` and optional `composition`. Groups cannot mix child blocks with leaf payload fields. See [dynamic composition](dynamic-composition.md) for nesting and inheritance rules.\n\n```json\n{\n "title": "Customer Feedback Summary",\n "blocks": [\n {\n "table": {\n "columns": ["Theme", "Mentions"],\n "rows": [\n ["Speed", 42],\n ["Ease of use", 31]\n ]\n }\n },\n {\n "quote": {\n "text": "The new workflow cut review time in half.",\n "attribution": "Operations Lead",\n "source": "Customer interview"\n }\n }\n ]\n}\n```\n\nAt slide root only, multiple content payload kinds are accepted as shorthand for the equivalent blocks form when there is no explicit `type`, no `blocks`, and no promoted region keys:\n\n```json\n{\n "title": "Habitat & Territory",\n "text": "Jaguars are strongly associated with presence of water and dense cover.",\n "items": [\n "Primary habitats include dense rainforests, swamps, and seasonally flooded wetlands.",\n "Solitary animals that establish and defend large territories."\n ]\n}\n```\n\nThe same shorthand works for other content kinds:\n\n```json\n{\n "title": "Evidence Snapshot",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Sightings"],\n "rows": [\n ["Q1", 12],\n ["Q2", 18]\n ]\n }\n },\n "quote": {\n "text": "Jaguar conservation depends on connected habitat.",\n "attribution": "Field researcher"\n }\n}\n```\n\n## Chart\n\nChart-specific fields are grouped under `chart`. Do not put loose chart data directly on a slide or region.\n\n```json\n{\n "title": "Revenue Trend",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Revenue", "Costs"],\n "rows": [\n ["Q1", 12, 8],\n ["Q2", 18, 11],\n ["Q3", 24, 15]\n ]\n }\n }\n}\n```\n\nInline chart data is tabular by default. Renderers convert `columns` and `rows` into series, axes, legends, and workbook data internally.\n\nAsset-backed data is still table-oriented:\n\n```json\n{\n "chart": {\n "type": "column",\n "data": {\n "src": "asset:revenue-csv",\n "columns": ["Quarter", "Revenue"]\n }\n }\n}\n```\n\n## Table\n\nTable-specific fields are grouped under `table`. Do not put loose `columns` or `rows` directly on a slide or region.\n\n```json\n{\n "title": "Pipeline",\n "table": {\n "columns": ["Stage", "Count", "Value"],\n "rows": [\n ["Qualified", 42, "$1.2M"],\n ["Proposal", 18, "$840K"]\n ]\n }\n}\n```\n\nTable body cells accept strings, numbers, booleans, or `null`. Since core 0.5.0, a cell or column header also accepts the same `TextRun[]` used by rich text:\n\n```json\n{\n "table": {\n "columns": [["Quarter ", {"text": "growth", "bold": true}], "Value"],\n "rows": [\n [["Up ", {"text": "12%", "color": "#008800"}], 12]\n ]\n }\n}\n```\n\nUse the current coordinated Node 24 train: core 0.11.0, renderer 0.9.0, editor 0.8.0 and PPTX 0.9.1. See the [compatibility matrix](compatibility-matrix.md) for exact pins and evidence. Core measures run styles when checking overflow and keeps each row intact when paginating. The renderer traces rich cells for the editor\'s existing formatting, typing and undo controls; the exporter emits editable native text runs. PPTX 0.4.0 introduced import of supported native character styles, paragraph defaults, theme fonts/colors, external links and significant whitespace as rich runs. Unstyled body cells remain strings, and cached display text cannot recover original scalar types or live fields. Conditional table styles, merged geometry and cell fills/borders/alignment remain limited; native PowerPoint visual parity is not yet verified.\n\nCore 0.6.0 adds `layoutTable` from `@openpresentation/opf/composition`. It measures scalar and rich cells, keeps short rows compact, and gives wrapped or multiline rows the height they need. When space is constrained it reduces spare row height before shrinking text, and reports overflow when the minimum fitting size cannot fit. Pass the same `scale`, font family, measurement provider and effective `minFontSize` to each consumer. The returned row boxes, cell text boxes and fits are shared by the coordinated SVG and PPTX implementations; rich table cells use uniform line advances to match native cell paragraph spacing. Native viewer fidelity remains a separate verification boundary.\n\n\n## Code\n\nCode-specific fields are grouped under `code`. A string value is shorthand for `code.source`; use object form when syntax highlighting or a file label matters. In object form, `source` is required.\n\n```json\n{\n "title": "Decision Rule",\n "code": {\n "source": "if risk > threshold:\\n escalate(owner)\\nelse:\\n approve(change)",\n "language": "python",\n "filename": "decision.py"\n }\n}\n```\n\n## Metric\n\nMetric-specific fields are grouped under `metric`. A string or number value is shorthand for `metric.value`; numeric values stay numeric and are formatted by renderers at display time. Use object form when labels, descriptions, units, deltas, or trends matter.\n\nThe `number-1x` through `number-6x` layout IDs declare one title placeholder and one through six `metric` placeholders. The IDs retain their existing names; the content kind and payload key are `metric`, not `number` or `text`. For several metrics, use separate `{ "metric": ... }` entries in `blocks`. Choosing a layout does not reinterpret existing text as numeric data.\n\n```json\n{\n "title": "Operating Metric",\n "metric": {\n "value": "42%",\n "label": "Review cycle reduction",\n "description": "Median reduction across customer review workflows.",\n "delta": "+11 pts",\n "trend": "up"\n }\n}\n```\n\n## Quote\n\nQuote-specific fields are grouped under `quote`. A string value is shorthand for `quote.text`; use object form when attribution or citation matters.\n\n```json\n{\n "title": "Customer Proof",\n "quote": {\n "text": "The new workflow made exceptions visible before they became escalations.",\n "attribution": "VP Operations, Acme Corp",\n "source": "Customer interview"\n }\n}\n```\n\n## Timeline\n\nTimeline-specific fields are grouped under `timeline`. An array value is shorthand for `timeline.events`; use object form when the timeline needs a name or description. Timeline events use `when`, `what`, and `description`.\n\n```json\n{\n "title": "Rollout Plan",\n "timeline": {\n "name": "Regional Rollout",\n "description": "Major milestones for the rollout.",\n "events": [\n {\n "when": "Q1",\n "what": "Pilot",\n "description": "Launch with one operations team."\n },\n {\n "when": "Q2",\n "what": "Rollout",\n "description": "Expand to all regions."\n }\n ]\n }\n}\n```\n\n## Regions\n\nRegion keys address a 3\xD73 grid of rows (`top`, `middle`, `bottom`) and columns (`left`, `center`, `right`):\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n```\n\n- A bare column key (`left`) spans all three rows; a bare row key (`top`) spans all three columns.\n- `+` spans adjacent rows or columns: `center+right`, `top+middle`.\n- `row:column` combines the two: `top:left`, `middle+bottom:center+right`.\n- Keys on one slide must not overlap, and regions cannot be mixed with root payload fields.\n\nSpans compose into common slide shapes:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThe same payload objects work inside regions \u2014 here, the sidebar-plus-main shape:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": {\n "table": {\n "columns": ["Metric", "Value"],\n "rows": [\n ["Revenue", "$4.2M"],\n ["Gross margin", "68%"]\n ]\n }\n },\n "center+right": {\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Month", "Revenue"],\n "rows": [\n ["Jan", 3.4],\n ["Feb", 3.8],\n ["Mar", 4.2]\n ]\n }\n }\n }\n}\n```\n'
26
32
  },
27
33
  {
28
34
  "slug": "data-import",
29
35
  "file": "docs/data-import.md",
30
36
  "title": "CSV and JSON data in OPF",
31
- "markdown": '# CSV and JSON data in OPF\n\nImport CSV, TSV, and JSON as ordinary inline tables or charts. The resulting OPF stays editable in the browser and works with PPTX export without needing the original file.\n\n## Editor\n\nClick **Import data** in the editor toolbar. Paste data or select a `.csv`, `.tsv`, or `.json` file. Choose Table or Chart, review the slide preview, and import. Charts let you choose the category column and numeric series. You can insert a new slide or replace a selected table/chart, including one inside a nested block. Imports are one undoable operation.\n\nThe first CSV/TSV row supplies column names by default. Uncheck that option for headerless data. JSON supports:\n\n- An array of records: `[{"Quarter":"Q1","Revenue":12},{"Quarter":"Q2","Revenue":18}]`.\n- A matrix with a header row: `[["Quarter","Revenue"],["Q1",12],["Q2",18]]`.\n- An explicit table: `{"columns":["Quarter","Revenue"],"rows":[["Q1",12],["Q2",18]]}`.\n\nAll record keys become columns in first-seen order. Missing record fields become null. Nested objects and arrays in cells must be flattened before import. Ragged rows, duplicate column names, malformed CSV, and invalid JSON produce errors.\n\nCSV table values stay strings, preserving identifiers such as `001` and exact input text. JSON scalar cell types are retained. Chart series convert strict numeric strings to numbers. Blank, null, boolean, currency-formatted, percentage-formatted, and ambiguous numeric values are rejected as measures; they are never silently replaced with zero. Numeric category labels remain categories. Choose one nonnegative series for pie/donut charts.\n\nThe browser preview supports imported column, bar, line, area, pie, and donut charts, including multiple series for the first four types. Large tables or long labels can still need layout adjustments or pagination.\n\n## CLI\n\nUse the [installable CLI preview](../packages/cli/README.md):\n\n```sh\nopf import-data revenue.csv --as table --output table.opf.json\nopf import-data revenue.json --as chart --chart-type line --output chart.opf.json\nopf import-data revenue.csv --as chart --category Quarter --series \'["Revenue","Costs"]\' --into deck.opf.json --in-place\nopf import-data revised.csv --as table --into deck.opf.json --path /slides/0/blocks/0/table --output reviewed.opf.json\n```\n\n`--into` appends a new data slide unless `--path` names an existing content container\'s `/table` or `/chart` field. The parent must already exist. The complete resulting document must validate. Unrelated fields remain intact. Without `--output` or `--in-place`, the document goes to stdout for review or piping. Existing output files require `--force`.\n\nUse `--format csv|tsv|json` to override format detection, `--delimiter \';\'` for semicolon CSV, `--no-header` for row arrays without labels, `--columns \'["Quarter","Revenue"]\'` to select/reorder columns, and `--title` to name a new data slide. `--series` and `--columns` accept JSON arrays so column names can contain commas. `-` reads data from stdin.\n\n## Package API\n\n```js\nimport {parseTabularData, createDataContent} from \'@openpresentation/opf/data\';\n\nconst csv = \'Quarter,Revenue,Costs\\nQ1,12,8\\nQ2,18,10\';\nconst table = createDataContent(csv, {as: \'table\', format: \'csv\'});\nconst chart = createDataContent(csv, {\n as: \'chart\', format: \'csv\', chartType: \'line\',\n category: \'Quarter\', series: [\'Revenue\', \'Costs\'],\n});\nconst document = {slides: [{title: \'Quarterly data\', blocks: [table, chart]}]};\nconst data = parseTabularData(csv); // {columns, rows}, with CSV strings preserved\n```\n\nThe functions also accept already-parsed JSON and are re-exported by `@openpresentation/opf-editor/data`. They are synchronous and browser-safe. Hosts read files with `File.text()` or Node\'s file APIs and pass their contents in. Neither function fetches URLs, resolves asset references, or reads files automatically.\n\nThis is an embedded data snapshot, not a live file link. OPF\'s existing `ChartDataSource` can declare a source reference, but source loading/refresh is a separate host responsibility. Tables use inline `columns`/`rows`; there is no new unsupported `table.src` field. Re-import after a source changes. These APIs are in the local coordinated preview packages; check package versions before using a previously published release.\n\n## Verification\n\n`node packages/javascript/test/data.mjs` checks parsing and mapping. `pnpm test:cli:packed` tests the installed CLI including data import. `pnpm test:data` verifies SVG series/signs and PPTX export/import. After `pnpm demo:editor`, open `/data-tests.html` on the editor server for file upload, preview, insertion, replacement, validation, and undo checks.\n'
37
+ "markdown": '# CSV and JSON data in OPF\n\nImport CSV, TSV, and JSON as ordinary inline tables or charts. The resulting OPF stays editable in the browser and works with PPTX export without needing the original file.\n\n## Editor\n\nClick **Import data** in the editor toolbar. Paste data or select a `.csv`, `.tsv`, or `.json` file. Choose Table or Chart, review the slide preview, and import. Charts let you choose the category column and numeric series. You can insert a new slide or replace a selected table/chart, including one inside a nested block. Imports are one undoable operation.\n\nThe first CSV/TSV row supplies column names by default. Uncheck that option for headerless data. JSON supports:\n\n- An array of records: `[{"Quarter":"Q1","Revenue":12},{"Quarter":"Q2","Revenue":18}]`.\n- A matrix with a header row: `[["Quarter","Revenue"],["Q1",12],["Q2",18]]`.\n- An explicit table: `{"columns":["Quarter","Revenue"],"rows":[["Q1",12],["Q2",18]]}`.\n\nAll record keys become columns in first-seen order. Missing record fields become null. Nested objects and arrays in cells must be flattened before import. Ragged rows, duplicate column names, malformed CSV, and invalid JSON produce errors.\n\nCSV table values stay strings, preserving identifiers such as `001` and exact input text. JSON scalar cell types are retained. Chart series convert strict numeric strings to numbers. Blank, null, boolean, currency-formatted, percentage-formatted, and ambiguous numeric values are rejected as measures; they are never silently replaced with zero. Numeric category labels remain categories. Choose one nonnegative series for pie/donut charts.\n\nThe browser preview supports imported column, bar, line, area, pie, and donut charts, including multiple series for the first four types. Large tables or long labels can still need layout adjustments or pagination.\n\n## CLI\n\nUse the published [CLI 0.9.0](../packages/cli/README.md) on Node 24:\n\n```sh\nopf import-data revenue.csv --as table --output table.opf.json\nopf import-data revenue.json --as chart --chart-type line --output chart.opf.json\nopf import-data revenue.csv --as chart --category Quarter --series \'["Revenue","Costs"]\' --into deck.opf.json --in-place\nopf import-data revised.csv --as table --into deck.opf.json --path /slides/0/blocks/0/table --output reviewed.opf.json\n```\n\n`--into` appends a new data slide unless `--path` names an existing content container\'s `/table` or `/chart` field. The parent must already exist. The complete resulting document must validate. Unrelated fields remain intact. Without `--output` or `--in-place`, the document goes to stdout for review or piping. Existing output files require `--force`.\n\nUse `--format csv|tsv|json` to override format detection, `--delimiter \';\'` for semicolon CSV, `--no-header` for row arrays without labels, `--columns \'["Quarter","Revenue"]\'` to select/reorder columns, and `--title` to name a new data slide. `--series` and `--columns` accept JSON arrays so column names can contain commas. `-` reads data from stdin.\n\n## Package API\n\n```js\nimport {parseTabularData, createDataContent} from \'@openpresentation/opf/data\';\n\nconst csv = \'Quarter,Revenue,Costs\\nQ1,12,8\\nQ2,18,10\';\nconst table = createDataContent(csv, {as: \'table\', format: \'csv\'});\nconst chart = createDataContent(csv, {\n as: \'chart\', format: \'csv\', chartType: \'line\',\n category: \'Quarter\', series: [\'Revenue\', \'Costs\'],\n});\nconst document = {slides: [{title: \'Quarterly data\', blocks: [table, chart]}]};\nconst data = parseTabularData(csv); // {columns, rows}, with CSV strings preserved\n```\n\nThe functions also accept already-parsed JSON and are re-exported by `@openpresentation/opf-editor/data`. They are synchronous and browser-safe. Hosts read files with `File.text()` or Node\'s file APIs and pass their contents in. Neither function fetches URLs, resolves asset references, or reads files automatically.\n\nThis is an embedded data snapshot, not a live file link. OPF\'s existing `ChartDataSource` can declare a source reference, but source loading/refresh is a separate host responsibility. Tables use inline `columns`/`rows`; there is no new unsupported `table.src` field. Re-import after a source changes.\n\nThese APIs are published in core 0.11.0 and re-exported by editor 0.8.0; CLI 0.9.0 includes `import-data`. Use the coordinated Node 24 train with renderer 0.9.0 and PPTX 0.9.1 for preview/export. Exact pins and compatibility boundaries are in the [compatibility matrix](compatibility-matrix.md) and [release plan](../release-plan.json).\n\n## Verification\n\n`node packages/javascript/test/data.mjs` checks parsing and mapping. `pnpm test:cli:packed` tests the installed CLI including data import. `pnpm test:data` verifies SVG series/signs and PPTX export/import. After `pnpm demo:editor`, open `/data-tests.html` on the editor server for file upload, preview, insertion, replacement, validation, and undo checks.\n'
32
38
  },
33
39
  {
34
40
  "slug": "design-resolution",
35
41
  "file": "docs/design-resolution.md",
36
42
  "title": "Design Resolution",
37
- "markdown": '# Design Resolution\n\nHow an engine decides the effective design for any given slide. The schema spreads these rules across field descriptions; this page states them once, as an algorithm.\n\n## Precedence\n\nFor every design field independently, the most specific source wins:\n\n```\n wins +--------------------------------------------------------+\n ^ | 1. slide design slides[i].design.* |\n | +--------------------------------------------------------+\n | | 2. deck design design.* on the presentation root |\n | +--------------------------------------------------------+\n | | 3. resolved theme colorScheme, fontScheme, |\n | | background, dimensions from the |\n | | theme record |\n | +--------------------------------------------------------+\n loses | 4. engine defaults e.g. spec/reference/ |\n v | engine-defaults.json |\n +--------------------------------------------------------+\n```\n\n1. **Slide design** \u2014 `slides[].design.*`\n2. **Deck design** \u2014 `design.*` on the presentation root\n3. **Resolved theme** \u2014 defaults carried by the theme record (`colorScheme`, `fontScheme`, `background`, `dimensions`)\n4. **Engine defaults** \u2014 engine configuration such as [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json)\n\nResolution is **per field**, not per object. A slide that sets only `design.contentAlignment` inherits everything else from the deck design; a deck that sets only `design.colorScheme` keeps the theme\'s font scheme and background.\n\nTwo field-level rules complete the picture:\n\n- **Base-plus-overrides within one object.** Wherever a reference object carries an `id` (`Theme`, `ColorScheme`, `FontScheme`), the `id` resolves a catalog record as the base and sibling fields override the resolved record per key. The string shorthand (`"colorScheme": "cool-horizon"`) is equivalent to setting only `id`.\n\n ```\n "colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" }\n\n catalog record "cool-horizon" sibling fields on the object\n accent1: "#2874A6" <-- replaced -- accent1: "#0F4C81"\n accent2: "#1B4F72" <-- kept\n light1: "#FFFFFF" <-- kept\n |\n v\n effective scheme: accent1 from the override, everything else\n from the record\n ```\n- **Explicit suppression.** `watermark`, `header`, and `footer` accept `false` to switch off an inherited value \u2014 distinct from omitting the field, which inherits.\n\nCatalog lookups inside this chain follow the standard resolution order (inline `catalogs.<kind>.records[]` \u2192 `catalogs.<kind>.source` \u2192 default catalog); see [`how-opf-works.md`](./how-opf-works.md).\n\n## Worked example 1: color scheme through every level\n\n```json\n{\n "design": {\n "theme": "classic",\n "colorScheme": "forest-green"\n },\n "slides": [\n { "title": "Inherits the deck" },\n {\n "title": "Slide override",\n "design": {\n "colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" }\n }\n }\n ]\n}\n```\n\n- Slide 1: the `classic` theme record supplies its own default color scheme, but the deck design sets `colorScheme` explicitly, so `forest-green` wins (level 2 beats level 3). Fonts, background, and dimensions still come from `classic`.\n- Slide 2: slide design beats deck design (level 1 beats level 2). The `cool-horizon` record resolves as the base, then `accent1` is replaced by `#0F4C81`. All other `cool-horizon` slots survive.\n\nThere is no ambiguity between "override" and "reference": every scheme value *is* a reference, and any sibling fields on the same object are overrides applied after the reference resolves.\n\n## Worked example 2: backgrounds and suppression\n\n```json\n{\n "design": {\n "theme": "dark",\n "background": "light1",\n "footer": {\n "left": { "text": "Acme Corp" },\n "right": { "slideNumber": true }\n }\n },\n "slides": [\n { "title": "Light slide in a dark theme" },\n {\n "title": "Section divider",\n "design": {\n "background": {\n "type": "gradient",\n "gradient": {\n "angle": 90,\n "stops": [\n { "color": "#0B1B2B", "position": 0 },\n { "color": "#123A5F", "position": 1 }\n ]\n }\n },\n "footer": false\n }\n }\n ]\n}\n```\n\n- Slide 1: the deck-level `background: "light1"` overrides the `dark` theme\'s default background. `light1` is a theme slot \u2014 it resolves through the effective color scheme, which itself resolved through the chain above.\n- Slide 2: the gradient replaces the deck background for this slide only, and `footer: false` suppresses the inherited footer rather than inheriting or replacing it.\n\n## Worked example 3: font scheme models\n\n```json\n{\n "design": {\n "fontScheme": {\n "id": "aptos",\n "code": { "family": "JetBrains Mono" }\n }\n }\n}\n```\n\nThe `aptos` record supplies the OOXML pair (`major`/`minor`). The `code` role is an OPF-specific addition with no OOXML slot, so it layers on top without disturbing the pair. When serializing to PowerPoint, engines write `major`/`minor` to `majorFont`/`minorFont` and map abstract roles (`heading`, `body`) onto those slots; roles like `accent` and `code` are renderer concerns. The same slot-versus-role split applies to color schemes: OOXML slots (`accent1`\u2013`accent6`, `dark1/2`, `light1/2`) round-trip directly, abstract roles (`primary`, `text`, `surface`, \u2026) are mapped onto slots by the engine.\n\n## What is *not* part of this chain\n\nContent payloads carry no design controls in v1 \u2014 `position`, `fontSize`, per-payload colors and the like were deliberately kept out while the content model stabilizes (see [`content-item-design-overrides.md`](./content-item-design-overrides.md)). The design system above, plus layout hints (`titleAlignment`, `contentBox`, `chartPrimary`, \u2026), is the entire styling surface of an OPF document.\n'
43
+ "markdown": '# Design Resolution\n\nHow an engine decides the effective design for any given slide. The schema spreads these rules across field descriptions; this page states them once, as an algorithm.\n\n## Precedence\n\nFor every design field independently, the most specific source wins:\n\n```\n wins +--------------------------------------------------------+\n ^ | 1. slide design slides[i].design.* |\n | +--------------------------------------------------------+\n | | 2. deck design design.* on the presentation root |\n | +--------------------------------------------------------+\n | | 3. resolved theme colorScheme, fontScheme, |\n | | background, dimensions from the |\n | | theme record |\n | +--------------------------------------------------------+\n loses | 4. engine defaults e.g. spec/reference/ |\n v | engine-defaults.json |\n +--------------------------------------------------------+\n```\n\n1. **Slide design** \u2014 `slides[].design.*`\n2. **Deck design** \u2014 `design.*` on the presentation root\n3. **Resolved theme** \u2014 defaults carried by the theme record (`colorScheme`, `fontScheme`, `background`, `dimensions`)\n4. **Engine defaults** \u2014 engine configuration such as [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json)\n\nResolution is **per field**, not per object. A slide that sets only `design.contentAlignment` inherits everything else from the deck design; a deck that sets only `design.colorScheme` keeps the theme\'s font scheme and background.\n\nTwo field-level rules complete the picture:\n\n- **Base-plus-overrides within one object.** Wherever a reference object carries an `id` (`Theme`, `ColorScheme`, `FontScheme`), the `id` resolves a catalog record as the base and sibling fields override the resolved record per key. The string shorthand (`"colorScheme": "cool-horizon"`) is equivalent to setting only `id`.\n\n ```\n "colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" }\n\n catalog record "cool-horizon" sibling fields on the object\n accent1: "#2874A6" <-- replaced -- accent1: "#0F4C81"\n accent2: "#1B4F72" <-- kept\n light1: "#FFFFFF" <-- kept\n |\n v\n effective scheme: accent1 from the override, everything else\n from the record\n ```\n- **Explicit suppression.** `watermark`, `header`, and `footer` accept `false` to switch off an inherited value \u2014 distinct from omitting the field, which inherits.\n\nCatalog lookups inside this chain follow the standard resolution order (inline `catalogs.<kind>.records[]` \u2192 `catalogs.<kind>.source` \u2192 default catalog); see [`how-opf-works.md`](./how-opf-works.md).\n\n## Worked example 1: color scheme through every level\n\n```json\n{\n "design": {\n "theme": "classic",\n "colorScheme": "forest-green"\n },\n "slides": [\n { "title": "Inherits the deck" },\n {\n "title": "Slide override",\n "design": {\n "colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" }\n }\n }\n ]\n}\n```\n\n- Slide 1: the `classic` theme record supplies its own default color scheme, but the deck design sets `colorScheme` explicitly, so `forest-green` wins (level 2 beats level 3). Fonts, background, and dimensions still come from `classic`.\n- Slide 2: slide design beats deck design (level 1 beats level 2). The `cool-horizon` record resolves as the base, then `accent1` is replaced by `#0F4C81`. All other `cool-horizon` slots survive.\n\nThere is no ambiguity between "override" and "reference": every scheme value *is* a reference, and any sibling fields on the same object are overrides applied after the reference resolves.\n\n## Worked example 2: backgrounds and suppression\n\n```json\n{\n "design": {\n "theme": "dark",\n "background": "light1",\n "footer": {\n "left": { "text": "Acme Corp" },\n "right": { "slideNumber": true }\n }\n },\n "slides": [\n { "title": "Light slide in a dark theme" },\n {\n "title": "Section divider",\n "design": {\n "background": {\n "type": "gradient",\n "gradient": {\n "angle": 90,\n "stops": [\n { "color": "#0B1B2B", "position": 0 },\n { "color": "#123A5F", "position": 1 }\n ]\n }\n },\n "footer": false\n }\n }\n ]\n}\n```\n\n- Slide 1: the deck-level `background: "light1"` overrides the `dark` theme\'s default background. `light1` is a theme slot \u2014 it resolves through the effective color scheme, which itself resolved through the chain above.\n- Slide 2: the gradient replaces the deck background for this slide only, and `footer: false` suppresses the inherited footer rather than inheriting or replacing it.\n\n## Worked example 3: font scheme models\n\n```json\n{\n "design": {\n "fontScheme": {\n "id": "aptos",\n "code": { "family": "JetBrains Mono" }\n }\n }\n}\n```\n\nThe `aptos` record supplies the OOXML pair (`major`/`minor`). The `code` role is an OPF-specific addition with no OOXML slot, so it layers on top without disturbing the pair. When serializing to PowerPoint, engines write `major`/`minor` to `majorFont`/`minorFont` and map abstract roles (`heading`, `body`) onto those slots. `accent` has no slot; the `code` family is written directly on code runs. The same slot-versus-role split applies to color schemes: OOXML slots (`accent1`\u2013`accent6`, `dark1/2`, `light1/2`) round-trip directly, abstract roles (`primary`, `text`, `surface`, \u2026) are mapped onto slots by the engine.\n\n### Code font\n\nThe `code` role resolves per key like every other override:\n\n1. `code` on the effective `design.fontScheme` object;\n2. `code` on the resolved font-scheme record (the `consolas` and `courier-new` records carry `{ "family": "Consolas" }` and `{ "family": "Courier New" }`);\n3. otherwise **Roboto Mono**, the documented fallback that `@openpresentation/opf-render` bundles.\n\nThe heading and body families are never reused as the code fallback, so choosing `aptos` still gives Roboto Mono code unless the deck sets `code`. `resolveFontFamilies()` in `@openpresentation/opf` applies these rules for all engines.\n\n### Engine default font scheme\n\nThe last-resort font scheme applies only when neither the slide, the deck nor the resolved theme names one. Every bundled theme names a font scheme (`minimal` uses `aptos`), and engines default the theme to `minimal`, so a document with no `design` gets `aptos` in every engine.\n\nEvery engine shares one last resort, `aptos`, so a custom theme without `fontScheme` is measured, paginated, previewed and exported in the same fonts. `@openpresentation/opf` exports it as `DEFAULT_FONT_SCHEME` (`resolveScriptFonts()` uses it too), and [`engine-defaults.json`](../spec/reference/engine-defaults.json) records it as `fontScheme.pptx.latin`:\n\n| Engine | Last resort | Where |\n| --- | --- | --- |\n| Core pagination | `DEFAULT_FONT_SCHEME` (`aptos`) | `packages/javascript/src/pagination.ts` |\n| opf-render preview | `aptos` (`engineDefaults.fontScheme.pptx.latin`) | `src/svg.js` |\n| opf-editor composition and slide transfer | `aptos` | `src/font-defaults.js` |\n| opf-pptx export | `aptos` (`DEFAULTS.fontScheme`) | `src/index.js` |\n\n\n`fontScheme.google` (`roboto`, `noto-sans-sc`, `noto-sans`) is not read by any current engine. It is kept as the intended default for a future Google Slides exporter, whose output renders in Google-hosted fonts.\n\nAptos is not openly licensed, so no OPF package bundles it. Previews take the same path for the last resort as for any `aptos` deck:\n\n- **Estimated layout** (no `textMeasurement`): the SVG names `Aptos` and `Aptos Display`, and the default raster engine draws them with its bundled sans-serif fallback (Roboto).\n- **Measured layout** with the opf-render office font pack and `substitutionPolicy: "visual"`: `Aptos` and `Aptos Display` resolve to their visual replacements from the OPF font policy (FF-31, provisional: Roboto, 2.15% mean width difference, and Carlito, 1.8%), and the substitution report lists both. Before FF-31 both resolved to Carlito. This is a visual-only look-alike: a documented fallback and a known layout-fidelity gap until a metric-compatible replacement exists. The PPTX always names the selected font, Aptos, never the replacement. See [font-fidelity.md](font-fidelity.md#font-policy-ff-31).\n- **Measured layout under the default metric policy**, or with only the base pack: `font-unavailable` for Aptos, as for a document with no `design`. Supply licensed Aptos faces, allow visual substitution, or set a `fallbackFamily`.\n\nUntil FF-35 (font-fidelity-everywhere), core pagination, opf-render and opf-editor fell back to `roboto` while opf-pptx used `aptos`, so such a deck was measured in Roboto but exported with Aptos. None of the 126 bundled examples reaches the last resort: all 805 renderer golden rasters and all 126 exported PPTX files are byte-identical before and after the change. `packages/javascript/test/font-scheme-defaults.test.mjs` checks the shared default in core pagination, and each sibling repository has a parity test.\n\n### Unknown font scheme\n\nA font-scheme id that matches no inline or bundled record (`"fontScheme": "no-such-scheme"`, `{ "id": "no-such-scheme", ... }`, or a theme record that names one) is handled the same way in every engine. The document still validates, because an id may name a record from a catalog the engine has not loaded:\n\n1. The `DEFAULT_FONT_SCHEME` record (`aptos`) is the base. Sibling fields on an object reference still override it per key, so `{ "id": "no-such-scheme", "major": "Inter", "minor": "Inter" }` uses Inter, and a `code` role still applies.\n2. The engine reports one `unresolved-font-scheme` diagnostic: `{ code, path, id, fallback: "aptos", message }`. `path` is where the id is written: `slides.N.design.fontScheme`, `design.fontScheme`, or the `slides.N.design.theme` / `design.theme` reference whose record names it.\n3. An object without `id` is an inline scheme on the same base and reports nothing.\n\n`resolveFontSchemeReference(reference, lookup, path)` in `@openpresentation/opf` implements this rule. `resolveFontFamilies()` also falls back to the default scheme\'s families (Aptos Display, Aptos) when a scheme names no heading or body family, instead of Roboto. Authoring-time `lintPresentation()` already warns about the unknown id (`opf/catalog-reference`).\n\n| Engine | Diagnostic channel | Reported |\n| --- | --- | --- |\n| Core pagination | `paginatePresentation(..., { onDiagnostic })` | once per path per call |\n| opf-render preview | `renderSvg` / `renderSvgDeck` `onDiagnostic` | once per path per rendered slide |\n| opf-editor | `session.composeSlide` / `paginateSlide` `onDiagnostic` option | once per call |\n| opf-pptx export | `toPptx(..., { onDiagnostic })` | once per path per export |\n\nBefore FF-35b, core pagination and opf-editor measured such decks in Roboto and opf-render threw `catalog-resolution-failed`. opf-pptx already exported Aptos, but reported nothing. None of the 126 bundled examples names an unknown font scheme. The 805 example SVGs, the 805 golden rasters and the 126 exported PPTX files are byte-identical before and after the change.\n\n### Sibling agreement checks\n\nopf-render, opf-editor and opf-pptx run the same unknown-scheme cases as core (`test/default-font-scheme.mjs`). Each package keeps a local copy of the default, and in opf-editor of the resolver. Their checks against core\'s `DEFAULT_FONT_SCHEME`, `resolveFontSchemeReference` and `paginatePresentation` run only when the installed core exports them. Those checks are skipped today: the siblings install the published `@openpresentation/opf` 0.11.0, which predates FF-35. They activate in either of two ways:\n\n- **Sibling CI:** after a core release that includes FF-35 and FF-35b is published, and each sibling\'s `@openpresentation/opf` dependency and lockfile move to it. After that release, the local copies can import core directly.\n- **Core ecosystem CI** (`.github/workflows/ecosystem-ci.yml`), which links this checkout\'s core into pinned sibling commits and runs their `npm test`: after those pins move to sibling commits that contain the FF-35 and FF-35b tests (the program\'s sibling pin bump).\n\nUntil then, the equality with core is established by running the sibling tests against a locally linked core.\n\n## Color references in content\n\nContent color fields (`TextRun.color`, styled table cell `style.fill` / `style.color`, table cell border `color`) accept references as well as literal hex, and those references resolve through the same chain above:\n\n```\n "color": "accent2" slot name -> effective color scheme slot\n "color": "text" role name -> role-to-slot mapping, then the slot\n "color": "var:risk" variable -> top-level variables map\n "color": "#B42318" literal -> used as-is (frozen at authoring time)\n```\n\n- **Slot names** (`accent1`\u2013`accent6`, `dark1`, `dark2`, `light1`, `light2`, `hyperlink`, `followedHyperlink`) read the named slot from the *effective* color scheme \u2014 the one produced by the slide \u2192 deck \u2192 theme \u2192 engine-default precedence at the top of this page. A slide-level `design.colorScheme` override therefore recolors that slide\'s named runs too.\n- **Role names** (`primary`, `secondary`, `accent`, `background`, `surface`, `text`, `textSecondary`) resolve through the same role handling engines already apply to color schemes: a role defined on the effective scheme is used directly; otherwise the engine maps the role onto a slot exactly as it does when serializing schemes.\n- **Variable references** (`var:<id>`) resolve against the document\'s top-level `variables` map, independent of the scheme. Variables are deck-scoped named colors \u2014 use them for values that have meaning (`var:risk`) or repeat across slides. An unknown id is a validation warning, never an error, and engines fall back to their default text color.\n\nThe styled table cell and border color fields enforce the reference forms at the schema level (a typo like `"acent2"` is a schema error there \u2014 neither hex, a known name, nor a `var:` reference). Run colors stay open strings so imported decks keep validating: an unrecognized run color is a validation warning, and renderers fall back to the theme text color \u2014 the same warn-don\'t-error posture unknown catalog ids get. Unknown `var:` ids are warnings everywhere.\n\n`@openpresentation/opf` exports `resolveColorRef()` with the shared slot, role, variable, and hex rules above so renderers and exporters do not drift. Pass the effective color scheme, optional resolved role colors, the deck `variables` map, and a theme-text `fallback` for unrecognized references.\n\n## Script fonts and language\n\nOOXML gives each theme font (major and minor) three script slots: `latin`, East Asian (`ea`) and complex script (`cs`). The presentation `language` and the effective font scheme resolve to all three:\n\n```\n latin design font scheme heading/body (the chain above)\n eastAsian 1. design.fontScheme.eastAsian explicit slot\n complexScript 2. the scheme\'s own major/minor when languageFamily is ea / cs and its\n languages list is empty or names the language\n 3. the language\'s font scheme when the language\'s script uses the slot\n 4. the latin family otherwise\n```\n\n- A language record\'s `script` (ISO 15924) picks its slot. East Asian scripts (`Jpan`, `Hans`, `Hant`, `Kore`, ...) use `eastAsian`. Complex scripts (`Arab`, `Hebr`, `Deva`, `Thai`, ...) use `complexScript`. Latin, Cyrillic, Greek and other scripts use `latin`. `direction` defaults from the script (Arabic and Hebrew are right-to-left).\n- The language\'s `fontScheme` applies to PowerPoint output and `googleFontScheme` to Google Slides output. For Latin-script languages, the design font scheme always supplies the latin slot.\n- A Latin deck therefore repeats its heading/body family in `ea`/`cs`. A Japanese deck with `design.fontScheme: { "major": "Carlito", "minor": "Carlito" }` keeps the Latin family in `latin` and uses Meiryo (PowerPoint) or Noto Sans JP (Google Slides) in `ea`. `design.fontScheme.eastAsian` / `.complexScript` (`{ "major": ..., "minor": ... }`) name a script font explicitly, for example for CJK text inside a Latin deck.\n\n`@openpresentation/opf` exports `resolveScriptFonts(document, { app, slideIndex })`, which returns the heading and body slots, the OOXML `lang` (a curated `ooxmlLang` culture tag such as `ja-JP` or `ms-MY`, or an authored region tag), the canonical `bcp47` tag, `script`, `direction`/`rtl`, and the per-script supplemental theme font. Renderers and exporters should use it rather than re-deriving slots. The model, the OOXML mapping and the open questions are in [`programs/font-fidelity-everywhere/script-font-model.md`](./programs/font-fidelity-everywhere/script-font-model.md). The renderer and exporter adopt it in separate changes, so their output is unchanged by this model alone.\n\n## What is *not* part of this chain\n\nBeyond the color references above, content payloads carry no design controls in v1 \u2014 `position`, `fontSize` overrides at payload level, and the like were deliberately kept out while the content model stabilizes (see [`content-item-design-overrides.md`](./content-item-design-overrides.md); styled table cells and rich-text runs carry the only per-content styling, and their color fields take the reference forms above). The design system, plus layout hints (`titleAlignment`, `contentBox`, `chartPrimary`, ...) and dynamic composition, is the styling surface of an OPF document.\n'
38
44
  },
39
45
  {
40
46
  "slug": "dynamic-composition",
41
47
  "file": "docs/dynamic-composition.md",
42
48
  "title": "Dynamic composition",
43
- "markdown": '# Dynamic composition\n\nOPF keeps authoring intent in JSON. Use `blocks` when content can reflow; use promoted regions when relative placement is meaningful. `composition` on a slide overrides fields in the resolved layout\'s `composition`. Existing documents remain valid.\n\n```json\n{\n "name": "Decision brief",\n "slides": [{\n "title": "Make the main idea clear",\n "composition": { "mode": "row", "weights": [2, 1], "overflow": "error" },\n "blocks": [\n { "text": "The evidence and recommendation receive twice the width." },\n { "text": "The supporting detail receives the remaining width." }\n ]\n }]\n}\n```\n\n`auto` evaluates candidate grids using text fit and cell proportions. `columns` limits its candidates. `grid` uses `columns` if given, otherwise a grid based on the canvas shape. `row` uses one row; `column` uses one column. Items retain source order. Weights size columns except in column mode, where they size rows. Missing weights are 1; unused weights have no effect. A partially filled final row retains its grid tracks.\n\n`gap` defaults to 1/30 and `padding` to 0.08, both fractions of the canvas\'s shorter edge. Large gaps are reduced when necessary to keep cells positive. `minFontSize` defaults to 16 reference pixels at a 720-pixel short edge. The reference coordinate system uses 96 pixels per inch. Explicit inch dimensions override presets independently for each axis.\n\nHeadings reserve space according to their wrapped text. Content that exceeds the number of preset placeholders reflows together; it is not drawn over already-bound content. Promoted regions keep the 3\xD73 vocabulary, including standalone `top`, `middle`, and `bottom`. They ignore flow direction and track weights.\n\n## Unpublished shared headers and footers\n\nThe `codex/shared-furniture-20260910` candidate adds `layoutFurniture(slide, options)` and `geometry.furniture`, separate from body `items`. Composition identifies the changed available-space policy as `grid-score-v9`. Raw callers pass the presentation as `options.presentation`, resolved dimensions/fonts and the same measurement provider used by preview. `slideIndex` identifies source paths; optional `slideNumber` is the one-based displayed number.\n\nThe core resolver honors whole local header/footer overrides, including `false` and empty objects. Each zone retains its image and all configured text fields in source-aware parts. Literal text and dates preserve whitespace and empty strings. Organization and section values point to their metadata source; page numbers use the actual output sequence. A missing organization/section or `date: true` without a supported literal date produces `unresolved-content`; the implementation never consults a clock or invents source text.\n\n`furniture-flow-v1` gives each left/center/right zone 26% of the canvas width. Parts stack within a zone; the tallest zone sets the natural band height. Text uses at least the selected readability floor, with complete accepted source lines and optional measured outline placement. Header and footer bands reserve room before heading and body allocation. Irreducible text, conflicting bands or a heading displaced beyond the remaining space produce diagnostics; strict composition rejects them. No-furniture body geometry remains unchanged.\n\nPagination repeats these fields without putting them among body slices. An optional `page.repeatedMappings` records repeated heading/furniture and metadata paths while the existing `page.mappings` retains its body-fragment contract. Whole-deck pagination evaluates final output numbers, including preceding continuation pages, and rejects unresolved repeated content atomically. Renderer and editor reuse the accepted parts; literal text/date fields, including empty values, support direct canvas editing and undo. Generated labels remain tied to metadata.\n\nCandidate PPTX export draws the accepted text boxes and fitted images. Semantic furniture reimport, native Office acceptance, corpus review, clean installed-package verification and coordinated publication are still pending. The published versions do not expose this contract. Bounds/readability checks do not certify whole-slide design quality: long labels can wrap heavily in portrait zones, and outline agreement does not establish native font identity.\n\n## Nested groups\n\n### Unpublished shared content cards\n\nThe coordinated `codex/shared-metric-integration-20260910` source branches add `grid-score-v5`. For `design.contentBox: true`, each body leaf carries a `frameBox` at its outer allocation and a `box` padded inward by 12 reference pixels at a 720-pixel short edge, capped at one quarter of the frame\'s width or height. Scoring, accepted payload measurement, strict overflow and pagination all use that rounded interior. Headings remain unframed, nested groups keep their original padding, and explicit outer regions/track weights remain authoritative. Automatic candidates may change because their available content space changes.\n\nRaw composition callers pass their resolved deck flag as `composeSlide(slide, {contentBox: effectiveDesign.contentBox, ...options})`; a slide\'s explicit `design.contentBox: false` overrides it. Coordinated renderer, editor and whole-presentation pagination resolve this option for their callers. Consumers draw at `frameBox` and use the accepted `box` and payload internals without another inset. Published core 0.9.0 does not expose this behavior. Content cards do not make the incomplete chart/timeline density models complete or certify native raster fidelity.\n\nA block or promoted region can contain its own `blocks` and `composition`. The optional discriminator is `"type": "group"`. A group has at least one child and cannot mix children with leaf fields such as `text` or `image`.\n\n```json\n{\n "composition": { "mode": "row", "weights": [2, 1] },\n "blocks": [\n {\n "composition": { "mode": "column", "padding": 0.02 },\n "blocks": [{ "text": "Recommendation" }, { "text": "Supporting evidence" }]\n },\n { "text": "Context" }\n ]\n}\n```\n\nThe parent allocates a box to each group, then the group arranges its children inside that box. Group padding defaults to zero; padding and gap use the group\'s shorter edge. Only `minFontSize` and `overflow` inherit. A strict ancestor cannot be weakened by a child\'s `overflow: "warn"`. Font sizes remain relative to the canvas, not the group. Groups can nest up to 32 levels; cycles and deeper nesting fail with an explicit error.\n\nAutomatic grid scoring inspects descendant text using each descendant\'s explicit arrangement or geometric automatic seed. After selecting the parent\'s grid, it optimizes each child\'s automatic grid. This deterministic, bounded search avoids exponential combinations; it does not claim a globally optimal packing.\n\n`result.items` contains every leaf with its full source path and effective composition. `result.groups` contains group paths, outer bounds, and content bounds. The editor\'s `setGroupComposition(path, value)` validates and records undo/redo just like slide composition edits.\n\n## Inspecting and repairing layout\n\n```js\nimport { composeSlide } from \'@openpresentation/opf/composition\';\nconst result = composeSlide(deck.slides[0], { width: 1280, height: 720, layout: resolvedLayout });\nconsole.log(result.items); // Source paths, content, geometry, and text estimates\nconsole.log(result.diagnostics); // Path-specific text-overflow and small-cell messages\n```\n\nThis pure function expects a validated slide. The caller resolves catalog records and passes the canvas size. The rendering and export packages perform those steps at their boundaries. No network, DOM, system font, or AI dependency is required.\n\n### Explain automatic selection (core 0.8.0 and later)\n\nPass `explain: true` to return `result.explanation`. This opt-in API requires core 0.8.0; it is absent from core 0.7.0. Enabling explanations adds no measurement calls and does not change geometry, source content, reading order, weights or selected arrangements within the same engine version.\n\n```js\nconst result = composeSlide(slide, {...resolvedOptions, explain: true});\nfor (const decision of result.explanation.decisions) {\n console.log(decision.path, decision.reason, decision.selectedColumns);\n console.table(decision.candidates);\n}\nconsole.log(result.explanation.textMeasurement);\nconsole.log(result.explanation.unmeasuredPayloads);\n```\n\n`resolvedOptions` supplies the same dimensions, layout, fonts and optional width provider as the preview. Core 0.8.0 identifies its explanation as `grid-score-v2`; core 0.9.0 advances to `grid-score-v3` to include complete code metadata/body measurements. Both record containers in parent-before-child order. `lowest-score` reports the candidates actually tried; `configured-mode` respects resolved row/column/grid intent and returns no invented candidates. `promoted-regions` leaves region placement fixed and has no selected column count. Empty slides have no decisions. Automatic search tries one through `min(slotCount, columns ?? 6)` columns, in ascending order; ties retain the first candidate. Reserved placeholders count as slots. The schema caps an explicit candidate limit at twelve columns.\n\nEach candidate has `columns`, `rows`, `score` and additive `penalties`:\n\n| Penalty | Rule |\n| --- | --- |\n| `cellProportions` | Sum of `abs(log(cellAspect / 1.6))` for descendant leaves |\n| `fontReduction` | Reduction from 25 reference pixels for text-like leaves; quotes and code in core 0.9 sum requested-minus-fitted sizes across their parts, divided by canvas scale |\n| `textOverflow` | 1,000 per overflowing text-like leaf, complete quote or complete code payload, regardless of the number of internal failure reasons |\n| `tableOverflow` | 1,000 per table whose shared cell layout overflows |\n| `smallCells` | 100 per leaf narrower than 100 or shorter than 60 reference pixels |\n| `emptySlots` | 2 per unused position in the candidate grid\'s final row |\n\nScores are preference costs, not quality percentages or guarantees. Floating-point summation can make the component total differ slightly from `score`. Parent scoring uses descendant explicit arrangements or geometric automatic seeds; child automatic grids are optimized only after selecting the parent. Candidate scores therefore describe the bounded search, not a full assessment of the final optimized subtree. Heading fit remains in ordinary diagnostics, outside body-grid scoring. A strict-fit rejection exposes the explanation on `OPFCompositionError` when requested.\n\n`textMeasurement` is `estimated` without a provider and `provided` with one. A provided width function does not establish font provenance, glyph coverage, shaping or native raster fidelity. Text, rich text, lists, quotes, table cells and code in core 0.9 participate in the fit model. `unmeasuredPayloads` identifies images, video, charts, metrics and timelines whose complete internal layout is not assessed. Core 0.8 also reports code as incomplete; core 0.9 measures its filename/language/body and insets. Media aspect ratios, chart labels, metric labels and timeline annotations remain gaps. A zero score or empty diagnostics is not proof that those payloads fit.\n\nThis milestone exposes the existing search for inspection. Guarded layout repairs, automatic weight allocation, content-aware candidate improvements, a common payload-internal measurement model, CLI explanations and a canvas **Auto arrange** preview/undo operation remain subsequent work. It does not silently paginate or rewrite a document.\n\nWith `overflow: "warn"` (default), the result retains all text and returns diagnostics. SVG emits all lines and marks overflowing groups with `data-opf-overflow="true"`; text may extend beyond its box or canvas. Consumers can collect diagnostics using `onDiagnostic`. With `overflow: "error"`, the layout rejects content that does not fit. Shorten the affected content, give it more space, or explicitly split it into another slide. Use the explicit pagination transform below to produce additional editable slides.\n\nThe editor exposes `editor.composeSlide(index)` and `editor.setComposition(index, value)`. The latter validates the change, records JSON Patch history, and supports undo/redo.\n\n## Pagination\n\n```js\nimport { paginateSlide, paginatePresentation } from \'@openpresentation/opf/pagination\';\nconst { presentation, pages } = paginatePresentation(deck);\n// Review, save, render, or export `presentation`; pages maps output fragments to source paths.\nconst single = paginateSlide(deck.slides[0], { width: 1280, height: 720, minFontSize: 24 });\n```\n\nPagination is an authoring operation. It produces ordinary OPF slides; previews and PPTX export consume those exact pages. It preserves the input, body order, nested groups, promoted regions, rich-text formatting, and source text characters. Plain text and rich runs split at grapheme boundaries, preferring sentence/paragraph breaks and then word breaks. Lists split between items, tables between rows with column labels repeated, and code splits without rewriting its source. Indivisible payloads remain intact. Existing track weights continue to apply to positions on each resulting page.\n\nThe default readability target is 24 reference pixels. Core 0.8.0 returns slides that persist that floor in `composition.minFontSize`, including an already-fitting one-page result; existing higher minima and strict overflow policies remain intact. Quotes can raise their nominal body/footer sizes to the floor. Published small-format payloads such as table cells retain their earlier typography caps; the unpublished [readability-floor candidate](plans/readability-floor.md) removes those caps from shared text/list/table fitting. Pagination relies on the shared engine\'s estimates; it is not a guarantee that every host font renders identically. Headings repeat unchanged, speaker notes remain on the first page, and continuation IDs avoid existing deck IDs. `pages[].mappings` records full source/output paths and half-open text or item ranges. Text offsets use UTF-16, so source strings can be reconstructed exactly. Quote bodies split at grapheme boundaries and repeat complete attribution/source fields on each page. An irreducible footer rejects the whole operation, including a quote with an empty body after earlier content.\n\nIf a heading, individual list item, table row, or other atomic payload cannot fit on an otherwise empty page, `OPFPaginationError` returns actionable diagnostics. There is no partial output. `maxSlides` defaults to 100, and a layout-evaluation limit bounds work on pathological input. Specialized chart and timeline internals still require visual inspection; their complete density models remain outstanding.\n\nThe editor\'s `editor.paginateSlide(index)` is one validated transaction with undo/redo. It returns `{change, pagination}`. Editor 0.5.0 commits a one-page readability-policy change too; repeating the operation after the policy is recorded returns `change: null`. The playground includes an overflowing draft and **Split overflow** action. The CLI writes a new file and refuses to overwrite an existing one:\n\n```sh\nnode packages/cli/dist/index.js paginate input.opf.json output.opf.json\n```\n\n## Fidelity boundary\n\nThe shared engine provides identical body and heading geometry to SVG and editable PPTX export. Text measurements default to deterministic estimates. For actual font advances, use the shared provider described in [measured fonts](font-fidelity.md). Complex scripts, fallback fonts, PowerPoint text rendering, rich text, charts, tables, and images still need visual verification. Dynamic composition is not a guarantee of pixel-identical PowerPoint output. List density includes rich runs, descriptions and nesting via `fitList`, with the same hanging indents used in preview and export. Only text-like payloads currently receive content-density estimates; small-cell diagnostics also cover non-text content.\n\nSVG embeds raster data URI images locally. Remote and file images require a host resolver that supplies a raster data URI; otherwise they appear as placeholders. `strictAssets` rejects unresolved images. The runtime never fetches them.\n\nSee [the complete example](../examples/technical/dynamic-composition.opf.json) and [local ecosystem verification](ecosystem-development.md).\n\n\n## Metric internals (unreleased integration)\n\nThe candidate `layoutMetric(value, box, options)` export accepts a finite number, string, or `{value, unit?, label?, description?, delta?, trend?}`. Import it from the root or composition entrypoint after building this checkout. It is not included in published core 0.9.0. Coordinated source branches now consume it through composition, atomic pagination, SVG, editor and PPTX; candidate/registry adoption and native raster verification remain unfinished. The [primitive checkpoint](plans/shared-metric-layout.md) and [source integration checkpoint](plans/shared-metric-integration.md) distinguish their evidence and remaining gates.\n\nPass the allocated reference-pixel `box`, resolved heading/body `fonts`, `textMeasurement`, canvas `scale`, effective `minFontSize`, source `path` and optional `overflow: \'error\'`. `metric-flow-v1` returns separate value/unit/label/description/delta/trend parts in that order. Numeric zero is visible; scalar values keep the scalar path. Every provided field retains its original string or number in `sources[].value`. Source ranges address `String(value)` using UTF-16 offsets; the original spelling of a numeric JSON token is not available. No locale formatting, trend icon, case conversion or separator is invented. Empty optional strings retain source mappings with `visible: false`; the required empty value keeps a targetable blank line.\n\nThe allocator tries an adjacent value/unit baseline when both fit one line and the unit uses at most 35% of the cell width; otherwise it stacks the fields. Metadata has an eight-reference-pixel gap, with twelve pixels after the primary row. Related fields stay together rather than being separated by a percentage of the cell height. The value starts at up to 76 reference pixels (28% of cell height); label/unit/delta start at 23, description at 20 and trend at 18. Every requested size is raised to the chosen floor, scaled once. Natural metadata height gets space before reducing type. At most 48 arrangements are evaluated, each with at most 77 value-size trials; identical inputs and a deterministic measurement provider select the fitting candidate with least summed font reduction, preferring the first candidate on ties.\n\nEach part exposes requested/resolved styles and the same source-preserving line/segment representation used by code (`CodeTextFit`), measured with proportional heading/body fonts. CR/LF/CRLF, tabs, whitespace and grapheme boundaries remain exact. Consumers must reuse accepted line and segment positions, font sizes and styles rather than independently re-fit or normalize text. This API reports `provided` measurement when a provider is passed, without claiming that its glyph coverage or shaping is complete. Unsupported glyphs propagate the provider\'s error with the field path.\n\nPass `align: \'left\' | \'center\' | \'right\'` (default left) to the primitive. Its returned `alignment` and per-part `linePositions` give an absolute x origin and baseline for each `fit.sourceLines` entry, including blank lines. An inline value/unit pair moves together, with the gap following the actual value advance. Composition accepts host-resolved `contentAlignment`; an explicit slide `design.contentAlignment` overrides it. Renderer/export/pagination pass the effective design into the same operation. Alignment does not trigger a second font fit.\n\nCheck `overflow` before consuming parts. Irreducible text, invalid available space, parts outside the cell and overlapping occupied line boxes return field-specific diagnostics; strict mode throws `OPFCompositionError`. Invalid available boxes retain their dimensions and have no fit. These are advance-based line rectangles, not glyph outlines: the controlled browser evidence separately records small glyph overhangs. The API is a bounded internal allocator, not the complete layout-repair/Auto arrange operation or a native export fidelity guarantee.\n\n`grid-score-v4` now accounts for every metric part\'s font reduction and applies one overflow penalty per failing metric leaf. `item.metricLayout` is measured against the rounded accepted cell; `item.text`/`item.textStyle` alias the value fit/style. Field diagnostics obey strict ancestor policies, while explicit modes/weights/regions remain authoritative. Metrics leave this branch\'s advance-model `unmeasuredPayloads` list. Explicit pagination retains metrics atomically with complete source types/metadata and rejects irreducible fields without returning partial output. These source contracts still require coordinated consumer/browser/native verification before release.\n\n## Code internals (core 0.9.0 and coordinated packages)\n\nCore 0.9.0\'s `layoutCode(value, box, options)` API accepts the schema\'s string shorthand or `{source, language?, filename?}` object. It returns measured filename/language/body parts with exact original text, requested/resolved styles, readability floors, available boxes and diagnostics. Renderer/PPTX 0.7.0 and editor 0.6.0 are its coordinated release targets for shared rendering, native export, source/metadata edits and undo. [Release gates](plans/shared-code-release.md) distinguish prepared versions from verified publication. Core 0.8.0 does not include this API and retains the [recorded code-label, filename and whitespace defects](plans/layout-repair.md).\n\n`grid-score-v3` charges code font reductions across all metadata/body parts and one overflow penalty per failing leaf. It preserves explicit modes, weights, regions and source order. Accepted `item.codeLayout` is fitted to the same rounded cell exposed as `item.box`; `item.text` and `item.textStyle` alias the body, not the first metadata part. Strict ancestor settings apply to internal `.source`, `.filename` and `.language` diagnostics. Code no longer appears in `explanation.unmeasuredPayloads`, which concerns the core advance-based model only. It does not mean browser/native fidelity is verified.\n\nPagination slices the code body at grapheme boundaries, repeats filename/language and returns contiguous UTF-16 body ranges while preserving all source bytes and the evaluated readability floor. Irreducible metadata rejects all output, including when the body is empty or earlier content could have fitted. Consumers must preview/export the returned document. The [integration checkpoint](plans/shared-code-integration.md) separates source, installed browser, Windows PowerPoint and remaining release gates.\n\nEach fitted part retains every space and explicit CR/LF/CRLF break. `fit.lines` contains exact source slices, and `fit.sourceLines` records half-open UTF-16 `start`, `end` and `nextStart` offsets, the measured width and a `soft`, `hard` or `end` boundary. A hard break occupies `[end, nextStart)`; soft wrapping consumes no source character. Joining `part.text.slice(line.start, line.nextStart)` reconstructs the original part. Blank lines and a final empty line are retained, and long tokens split only at grapheme boundaries. Filename and language text are not case-converted. An absent/empty metadata pair creates a generated `code` label with no source range.\n\n`code-flow-v1` uses 18-reference-pixel outer insets, an eight-pixel gap between filename and language, and a twelve-pixel gap before the body. Nominal metadata/body sizes are 14/18 reference pixels, raised when necessary to respect the selected minimum, then scaled once. At most four metadata nominal/floor combinations are tried; each body fit tries at most 19 sizes regardless of canvas scale. The fitting combination with least font reduction wins. Irreducible metadata/body failures retain all text and diagnostic paths; invalid available boxes have no fit. `overflow: \'error\'` rejects rather than returning partial output.\n\nTabs remain literal characters in part text and displayed-line slices. Measurement advances to the next multiple of four measured spaces from that line\'s origin; `fit.tabSize` and `fit.tabWidth` expose the rule. Each source line\'s `segments` contains exact text/tab source ranges plus measured `x`/`width` values relative to its origin. Consumers must reuse those positions: an Edge probe showed that SVG treats a tab as one space despite CSS `tab-size: 4`. The candidate SVG renderer uses positioned spans and geometric precision; native export uses accepted tab stops. Width measurements and source preservation alone do not establish glyph-outline containment, shaping/bidi support or native fidelity. [Installed workflow evidence](evidence/shared-code-installed/summary.json) records the separate actual browser and native checks with their exact font/runtime scope.\n\nCandidate native export stores source boundaries in standard PowerPoint shape tags. Complete unique groups recover exact code/source metadata, with current native text taking precedence. Missing, damaged or ambiguous groups retain visible native shapes and report diagnostics. Reimport does not reconstruct native formatting, positioning, font theme or readability policy. Eight installed-export wide/portrait slides pass native edit/save/reopen and all 24 original/saved/edited imports on the recorded Windows PowerPoint build; this is not arbitrary PowerPoint round-trip or pixel equivalence. The editor preserves untouched CRLF/CR source around edits and keeps committed preview geometry separate from its active native textarea caret.\n\nThe JSON schema can accept strings that [XML 1.0 cannot represent](https://www.w3.org/TR/xml/#charsets). Candidate SVG/PPTX code output rejects forbidden controls, unpaired UTF-16 surrogates, U+FFFE and U+FFFF with `invalid-code-text`, the source field path and UTF-16 offset in the message. The input stays unchanged; the caller can correct that character explicitly. Tabs, CR/LF/CRLF and valid supplementary characters remain accepted for serialization. Schema support, format representability and glyph coverage are separate properties.\n\nThe controlled SVG harness requests `text-rendering="geometricPrecision"` as well as explicit segment placement. Initial Linux Chromium CI rounded glyph advances under default hinting, unlike Windows Edge with the same font bytes. The [SVG specification](https://www.w3.org/TR/SVG/painting.html#TextRenderingProperty) defines geometric precision as a rendering hint, so consumers still need actual browser checks with their exact fonts and supported environments; the hint alone does not certify agreement. The harness retains a 0.1-reference-pixel tolerance and records observations before assertions.\n\n## Quote internals (core 0.8.0 and coordinated packages)\n\nCore 0.8.0 exports `layoutQuote(value, box, options)` from the root or composition entrypoint. Pass validated quote content (object or string shorthand), its allocated reference-pixel box, resolved `fonts`, `textMeasurement`, `scale` (canvas short edge / 720), effective `minFontSize`, `overflow` policy and its source `path`.\n\nThe result contains `parts` for the body and any nonempty footer, exact display `text`, source mappings, requested and resolved text styles, and the available boxes/fits. Source ranges use half-open UTF-16 offsets in both the source field and display string; generated quotation marks and the footer separator have no source range. The original content is never modified. A supplied width provider is reported as `provided`; it does not certify shaping or font fidelity.\n\nCheck `overflow` and `diagnostics` before accepting the parts. Invalid available dimensions remain visible with `fit` absent, and `overflow: \'error\'` throws `OPFCompositionError`. Diagnostics distinguish invalid part space, parts outside their cell, text that exceeds its reserved space, and overlapping line rectangles. Those rectangles are conservative text-layout bounds, not measured glyph outlines. The readability floor is scaled once and can raise the nominal body (28) or footer (17) size; it is never silently capped below the selected floor.\n\n`quote-flow-v1` keeps 18-reference-pixel outer insets and an 18-pixel body/footer gap while fonts scale with the canvas. A 40-pixel footer is a whitespace preference. The allocator expands it for long sources or compacts it for dense bodies, trying at most the nominal and minimum footer sizes and selecting the fitting pair with least total font reduction. If neither fits, it returns floor-size failure diagnostics. This is a bounded internal allocation step, not a complete layout-repair engine.\n\n`composeSlide` scores both parts and accepts geometry against the final rounded item box. Each quote item carries `quoteLayout`; its compatibility `text` field is the same fit object as the quote body, including generated quotation marks. Consumers needing original offsets must use the explicit `sources` mappings. The coordinated renderer and PPTX consume these parts without another measurement/style-resolution pass. Missing geometry or invalid part boxes reject rendering/export rather than omitting content. This requires core 0.8.0 with renderer/PPTX 0.6.0; older core 0.7.0/renderer 0.5.1/PPTX 0.5.2 lack these changes. The complete published set, immutable verification refs and fresh registry evidence are recorded in `release-plan.json` and [the release plan](plans/shared-quote-release.md).\n\nBrowser glyph bounds can extend slightly beyond advance-based part boxes into the reserved inset. Current loaded-font tests record those overhangs, verify glyph containment inside the full quote cell and check body/footer separation. Native PowerPoint fixtures separately verify text, sizes, cell containment, save/reopen and reimport. Neither test establishes universal pixel equivalence. Original requested-font provenance through host substitutions and non-quote payload internals remain open requirements.\n\n## Resizing in the preview\n\nChoose **Arrange** in the editor to reveal track dividers. Drag a divider to redistribute the space between adjacent columns (row/grid) or rows (column), including nested groups. Arrow keys make small changes; Shift makes larger changes. Escape discards a pointer draft. One drag creates one undo step, and no content is removed. Strict overflow rejects a resize that violates its fit constraints.\n\nResizing an automatic layout makes its chosen columns explicit as `mode: grid` with `columns`. This prevents the number of columns from changing under the pointer. The adjacent share clamps to 5\u201395%, with positive schema-valid weights. Other track proportions and unrelated document fields remain intact. Promoted regions retain their positions; their nested groups can still be resized. Layouts with reserved placeholder slots need an explicit arrangement first. Flows with more than twelve tracks need grouping before the current resize controls can express all weights.\n\n`createCanvasEditor(container, {layoutEditing: true, ...options})` enables dividers initially. `canvas.setLayoutEditing(boolean)` toggles them, and `canvas.commit()` / `canvas.cancel()` also handle an active resize. `onDraft` receives the proposed document; the session stays unchanged until commit. Changes to the resized container cancel a stale draft; unrelated updates are retained.\n\nThe shared engine exposes `geometry.flows`: each flow has its container path, content box, resolved column/row tracks (offset and size), clamped gap, effective composition, item count, and reserved slot count. This is renderer geometry, not new OPF document fields.\n\nAgents can prepare the same guarded change without a DOM:\n\n```js\nimport {prepareTrackResize} from \'@openpresentation/opf-editor/layout\';\nimport {resolvePresentation} from \'@openpresentation/opf-render/svg\';\nconst geometry = resolvePresentation(editor.document, renderOptions).slides[0].geometry;\nconst flow = geometry.flows.find(flow => flow.path === \'slides.0\');\nconst prepared = prepareTrackResize(editor.document, flow, 0, 0.65);\n// Boundary 0: give the first track 65% of the adjacent pair\'s combined space.\n// Preview prepared.document with the same renderer and font provider before applying.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n```\n\nThe patch contains a `test` guard for the container before changing its composition. Failed tests do not mutate the document or its history. A test-only patch is read-only. Rendering is preflighted by the canvas; headless callers should likewise render a candidate to enforce font and overflow constraints.\n\nVerification: editor layout model tests, `/layout-tests.html` browser keyboard checks and trusted-pointer specimens, and `pnpm test:layout` for measured SVG/native PPTX coordinate parity. Shape-coordinate checks do not establish PowerPoint raster pixel parity.\n\n\n## Reordering and moving blocks\n\nIn **Arrange**, drag a numbered block handle to reorder siblings. The insertion marker shows the destination; the shared renderer reflows the slide after drop. Arrow keys on a handle move the whole block earlier or later. Click a handle for **Earlier**, **Later**, or an explicit destination and insertion position. The destination menu supports existing groups and block-based slides, including moving a child out of a group or moving a whole group to another slide. `canvas.openBlockMenu(path)` opens the same controls programmatically.\n\nA move preserves the entire block and its nested content, formatting, data, and references. Parent composition weights describe positions, so they stay in place. Moving to another container can change the block\'s inherited design and readability constraints; the canvas renders the candidate before committing it. Strict overflow or an unavailable required font rejects the move. A move cannot leave an empty block container or put a group inside its own descendants. Move the group or add another block first when the source has only one child.\n\n```js\nimport {prepareBlockMove, listBlockContainers} from \'@openpresentation/opf-editor/layout\';\nconst containers = listBlockContainers(editor.document);\nconst prepared = prepareBlockMove(editor.document,\n \'/slides/0/blocks/0\', \'/slides/0/blocks/1\', 1);\n// Insert the first block before child 1 of the second block\'s group.\n// Destination indexes refer to the document before removal.\n// prepared.path reports the moved block\'s address after any index shifts.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n```\n\n`prepareBlockMove` returns `{document, patches, path, changed}`. It validates the complete result and emits guarded remove/add patches, so the editor or CLI can apply it atomically. No-op moves return `changed: false` and no patches. `listBlockContainers(document, {slideIndex})` optionally limits discovery to a single slide and excludes arbitrary extension data. Headless callers should render the candidate with their intended font provider before applying. The browser and installed-package block harnesses exercise nested moves, undo, stale menus, keyboard access, strict-fit rejection, and native drag reordering.\n\nCreation and deletion use the same layout engine: insertions can normalize implicit payloads into explicit blocks; deletions prune empty groups while retaining the slide. Existing track weights stay positional. See the [editor creation guide](live-editor.md#create-duplicate-and-delete-content) for the guarded APIs and canvas controls.\n'
49
+ "markdown": '# Dynamic composition\n\nOPF keeps authoring intent in JSON. Use `blocks` when content can reflow; use promoted regions when relative placement is meaningful. `composition` on a slide overrides fields in the resolved layout\'s `composition`. Existing documents remain valid.\n\nThe current published Node 24 train is core 0.11.0, renderer 0.9.0, PPTX 0.9.1, editor 0.8.0 and CLI 0.9.0. Use the exact pins in [release-plan.json](../release-plan.json); the [compatibility matrix](compatibility-matrix.md) separates package support from native Office and font gates. Older version references below identify when individual contracts were introduced.\n\n```json\n{\n "name": "Decision brief",\n "slides": [{\n "title": "Make the main idea clear",\n "composition": { "mode": "row", "weights": [2, 1], "overflow": "error" },\n "blocks": [\n { "text": "The evidence and recommendation receive twice the width." },\n { "text": "The supporting detail receives the remaining width." }\n ]\n }]\n}\n```\n\n`auto` evaluates candidate grids using text fit and cell proportions. `columns` limits its candidates. `grid` uses `columns` if given, otherwise a grid based on the canvas shape. `row` uses one row; `column` uses one column. Items retain source order. Weights size columns except in column mode, where they size rows. Missing weights are 1; unused weights have no effect. A partially filled final row retains its grid tracks.\n\n`gap` defaults to 1/30 and `padding` to 0.08, both fractions of the canvas\'s shorter edge. Large gaps are reduced when necessary to keep cells positive. `minFontSize` defaults to 16 reference pixels at a 720-pixel short edge. The reference coordinate system uses 96 pixels per inch. Explicit inch dimensions override presets independently for each axis.\n\nHeadings reserve space according to their wrapped text. Content that exceeds the number of preset placeholders reflows together; it is not drawn over already-bound content. Promoted regions keep the 3\xD73 vocabulary, including standalone `top`, `middle`, and `bottom`. They ignore flow direction and track weights.\n\n## Shared headers and footers\n\nPublished core 0.11.0 exposes `layoutFurniture(slide, options)` and `geometry.furniture`, separate from body `items`. Composition identifies the available-space policy as `grid-score-v9`. Raw callers pass the presentation as `options.presentation`, resolved dimensions/fonts and the same measurement provider used by preview. `slideIndex` identifies source paths; optional `slideNumber` is the one-based displayed number.\n\nThe core resolver honors whole local header/footer overrides, including `false` and empty objects. Each zone retains its image and all configured text fields in source-aware parts. Literal text and dates preserve whitespace and empty strings. Organization and section values point to their metadata source; page numbers use the actual output sequence. A missing organization/section, or `date: true` without a host-supplied current date, produces `unresolved-content`; the implementation never consults a clock or invents source text.\n\nSlide numbers and dates carry formats. `slideNumberFormat` is a template such as `"A-{current}"` or `"{current} / {total}"`: `{current}` is the displayed number and `{total}` is the displayed slide count (`options.slideCount`, else `presentation.slides.length`; whole-deck pagination iterates to the final page count). `dateFormat` is an LDML-style pattern (`yyyy`, `yy`, `MMMM`, `MMM`, `MM`, `M`, `dd`, `d`, `EEEE`, `EEE`, quoted literals) with fixed English month and weekday names. A string `date` with `dateFormat` must be an ISO `YYYY-MM-DD` date and renders as fixed, generated text tied to that source value; without `dateFormat` a date string stays literal and editable. `date: true` is the current date: hosts pass today\'s ISO date as `options.date` (composition, pagination, renderer and exporter), and the default pattern is `M/d/yyyy`. Text parts expose `fields` (half-open UTF-16 ranges of each `{current}` number and of a whole current date), so exporters can write native live fields while `{total}` and fixed dates stay fixed text. `formatFurnitureDate()` and `formatSlideNumber()` are exported for hosts. All configured fields in one zone stack, in the order image, text, organization, section, slide number, date; put a date and a slide number in different zones to keep one line each. Hiding furniture on a title slide is the slide-level `design.header: false` / `design.footer: false` override.\n\n`socials: true` (core 0.11.1 and later) generates one `socials` part from the primary organization\'s `organization.socials`. The part has one source line per platform, in key order, and a parallel `links` array (`platform`, `text`, `href`, `resolved`, `sourcePath`). `resolveSocialProfile(platform, value, records, owner)` formats each value without network access. A handle loses its `handlePrefix` and is substituted into `companyUrlPattern`, then `profileUrlPattern`, then `baseUrl/{handle}`; the result is shown as that URL without `https://`. A URL value passes through unchanged except that `https://` is dropped from the display. With no matching record, the value is shown raw with no link, which is the Socials engine fallback. Records come from inline `catalogs.socialPlatforms.records` first and then from host-supplied `options.socialPlatforms`. Composition never loads the bundled catalog itself; `paginatePresentation`, opf-render and opf-pptx pass it. A missing organization, or one with no non-empty socials, produces `unresolved-content`. Speaker socials, platform icons, brand colors and slide-size presets are not rendered.\n\n`furniture-flow-v2` gives each left/center/right zone 26% of the canvas width. Parts stack within a zone; the tallest zone sets the natural band height. Text uses at least the selected readability floor, with complete accepted source lines and optional measured outline placement. Header and footer bands reserve room before heading and body allocation. Irreducible text, conflicting bands or a heading displaced beyond the remaining space produce diagnostics; strict composition rejects them. No-furniture body geometry remains unchanged.\n\nPagination repeats these fields without putting them among body slices. An optional `page.repeatedMappings` records repeated heading/furniture and metadata paths while the existing `page.mappings` retains its body-fragment contract. Whole-deck pagination evaluates final output numbers, including preceding continuation pages, and rejects unresolved repeated content atomically. Renderer and editor reuse the accepted parts; literal text/date fields, including empty values, support direct canvas editing and undo. Generated labels remain tied to metadata.\n\nPublished PPTX 0.9.1 draws the accepted editable text boxes and fitted images and records furniture provenance in tagged slide shapes. Reimport uses current native text and images; damaged or ambiguous provenance retains visible content with diagnostics. The [fresh installed-package evidence](evidence/shipped-train-20260921/installed/acceptance-summary.json) includes deterministic export, current-content reimport controls and offline canvas editing/undo. Native PowerPoint acceptance, font compatibility and full visual review remain separate gates; this is not native `p:hf` Header/Footer support. Bounds/readability checks do not certify whole-slide design quality: long labels can wrap heavily in portrait zones, and outline agreement does not establish native font identity.\n\n## Slide-level images\n\nCore 0.11.1 and later resolve `design.slideImage` into `geometry.slideImage`, beside body `items`. It applies to a slide in three cases:\n\n- The slide sets its own `design.slideImage`.\n- The deck sets `design.slideImage` and the slide\'s layout record declares `slideImage: true`.\n- The deck sets `design.slideImage` and the slide\'s root `image` is the same source, as in the pptx.gallery image-treatment snippets.\n\nOther slides ignore a deck-level value, so existing decks keep their geometry: 81 bundled example decks set a deck-level slide image and none of them changes. When the value is the asset shorthand rather than a `{ position }` object, the layout\'s `slideImageAlignment` supplies the position, and `background` is the fallback.\n\n`background` gives the image the whole slide, and headings and content compose unchanged over it. `left`, `right`, `top` and `bottom` give the image half the slide, edge to edge, and headings and content compose in the other half with the usual padding. Header and footer bands keep their full-width placement. The frame uses `design.imageFill`, with `crop` as the default: `crop` covers the frame from the center and `fit` shows the whole image centered inside it. Without `design.imageFill`, the content-image default stays `fit`.\n\nThe slide\'s root `image` becomes the slide image, not a second content item, in two cases: the treatment object omits `src`, or `src` is the same source as the root image. A root image with a different source stays content. The result reports `path` (the configuring design value), `sourcePath` (where the drawn asset lives), `region`, `box` and `replacesContent`. Coordinated opf-render draws the frame beneath content. Coordinated opf-pptx exports one native `p:pic` at the same frame, with crop and fit written as `a:srcRect`. A tagged picture that has not been edited imports back as the slide\'s `design.slideImage`. The treatment vocabulary is covered in [image treatments](image-treatments.md): size, inset, aspect ratio, preset masks, line, opacity, grayscale or duotone, and overlay. That page also gives the support status of each pptx.gallery treatment.\n\n## Nested groups\n\n### Shared content cards\n\nShared content cards are published in core 0.10.0 and later, including current core 0.11.0. For `design.contentBox: true`, each body leaf carries a `frameBox` at its outer allocation and a `box` padded inward by 12 reference pixels at a 720-pixel short edge, capped at one quarter of the frame\'s width or height. Scoring, accepted payload measurement, strict overflow and pagination all use that rounded interior. Headings remain unframed, nested groups keep their original padding, and explicit outer regions/track weights remain authoritative. Automatic candidates may change because their available content space changes.\n\nEvery composed item carries its resolved horizontal text `alignment` (`left`, `center` or `right`). The title uses `titleAlignment`; every other item, including subtitle, tag, body text, lists, tables and metrics, uses `contentAlignment`. A slide\'s explicit design value wins over the host option, and the default is `left`. The title never inherits `contentAlignment`. Accepted outline placement and metric internals use the same value. The renderer and the PPTX exporter anchor preview and native text to `item.alignment`, so both engines place a layout\'s text the same way.\n\nRaw composition callers pass their resolved deck flag as `composeSlide(slide, {contentBox: effectiveDesign.contentBox, ...options})`; a slide\'s explicit `design.contentBox: false` overrides it. Coordinated renderer, editor and whole-presentation pagination resolve this option for their callers. Consumers draw at `frameBox` and use the accepted `box` and payload internals without another inset. Core 0.9.0 predates this behavior. Content cards do not make the incomplete chart/timeline density models complete or certify native raster fidelity.\n\nA block or promoted region can contain its own `blocks` and `composition`. The optional discriminator is `"type": "group"`. A group has at least one child and cannot mix children with leaf fields such as `text` or `image`.\n\n```json\n{\n "composition": { "mode": "row", "weights": [2, 1] },\n "blocks": [\n {\n "composition": { "mode": "column", "padding": 0.02 },\n "blocks": [{ "text": "Recommendation" }, { "text": "Supporting evidence" }]\n },\n { "text": "Context" }\n ]\n}\n```\n\nThe parent allocates a box to each group, then the group arranges its children inside that box. Group padding defaults to zero; padding and gap use the group\'s shorter edge. Only `minFontSize` and `overflow` inherit. A strict ancestor cannot be weakened by a child\'s `overflow: "warn"`. Font sizes remain relative to the canvas, not the group. Groups can nest up to 32 levels; cycles and deeper nesting fail with an explicit error.\n\nAutomatic grid scoring inspects descendant text using each descendant\'s explicit arrangement or geometric automatic seed. After selecting the parent\'s grid, it optimizes each child\'s automatic grid. This deterministic, bounded search avoids exponential combinations; it does not claim a globally optimal packing.\n\n`result.items` contains every leaf with its full source path and effective composition. `result.groups` contains group paths, outer bounds, and content bounds. The editor\'s `setGroupComposition(path, value)` validates and records undo/redo just like slide composition edits.\n\n## Inspecting and repairing layout\n\n```js\nimport { composeSlide } from \'@openpresentation/opf/composition\';\nconst result = composeSlide(deck.slides[0], { width: 1280, height: 720, layout: resolvedLayout });\nconsole.log(result.items); // Source paths, content, geometry, and text estimates\nconsole.log(result.diagnostics); // Path-specific text-overflow and small-cell messages\n```\n\nThis pure function expects a validated slide. The caller resolves catalog records and passes the canvas size. The rendering and export packages perform those steps at their boundaries. No network, DOM, system font, or AI dependency is required.\n\n### Explain automatic selection (core 0.8.0 and later)\n\nPass `explain: true` to return `result.explanation`. This opt-in API requires core 0.8.0; it is absent from core 0.7.0. Enabling explanations adds no measurement calls and does not change geometry, source content, reading order, weights or selected arrangements within the same engine version.\n\n```js\nconst result = composeSlide(slide, {...resolvedOptions, explain: true});\nfor (const decision of result.explanation.decisions) {\n console.log(decision.path, decision.reason, decision.selectedColumns);\n console.table(decision.candidates);\n}\nconsole.log(result.explanation.textMeasurement);\nconsole.log(result.explanation.unmeasuredPayloads);\n```\n\n`resolvedOptions` supplies the same dimensions, layout, fonts and optional width provider as the preview. Core 0.8.0 identifies its explanation as `grid-score-v2`; core 0.9.0 advances to `grid-score-v3` to include complete code metadata/body measurements. Current core 0.11.0 reports `grid-score-v9`. These versions record containers in parent-before-child order. `lowest-score` reports the candidates actually tried; `configured-mode` respects resolved row/column/grid intent and returns no invented candidates. `promoted-regions` leaves region placement fixed and has no selected column count. Empty slides have no decisions. Automatic search tries one through `min(slotCount, columns ?? 6)` columns, in ascending order; ties retain the first candidate. Reserved placeholders count as slots. The schema caps an explicit candidate limit at twelve columns.\n\nEach candidate has `columns`, `rows`, `score` and additive `penalties`:\n\n| Penalty | Rule |\n| --- | --- |\n| `cellProportions` | Sum of `abs(log(cellAspect / 1.6))` for descendant leaves |\n| `fontReduction` | Reduction from 25 reference pixels for text-like leaves; current quote/code/metric/timeline layouts sum requested-minus-fitted sizes across their parts, divided by canvas scale |\n| `textOverflow` | 1,000 per overflowing text-like leaf or complete quote/code/metric/timeline payload, regardless of the number of internal failure reasons |\n| `tableOverflow` | 1,000 per table whose shared cell layout overflows |\n| `smallCells` | 100 per leaf narrower than 100 or shorter than 60 reference pixels |\n| `emptySlots` | 2 per unused position in the candidate grid\'s final row |\n\nScores are preference costs, not quality percentages or guarantees. Floating-point summation can make the component total differ slightly from `score`. Parent scoring uses descendant explicit arrangements or geometric automatic seeds; child automatic grids are optimized only after selecting the parent. Candidate scores therefore describe the bounded search, not a full assessment of the final optimized subtree. Heading fit remains in ordinary diagnostics, outside body-grid scoring. A strict-fit rejection exposes the explanation on `OPFCompositionError` when requested.\n\n`textMeasurement` is `estimated` without a provider and `provided` with one. A provided width function does not establish font provenance, glyph coverage, shaping or native raster fidelity. Text, rich text, lists, quotes, table cells and code in core 0.9 participate in the fit model. Current core 0.11.0 also measures metric and timeline parts; its `unmeasuredPayloads` identifies images, video and charts whose complete internal layout is not assessed. Core 0.8 reports code as incomplete; core 0.9 measures its filename/language/body and insets. Media aspect ratios, chart labels and complete timeline visual density still require inspection. A zero score or empty diagnostics is not proof that those payloads fit.\n\nExplanations expose the search for inspection and do not silently paginate or rewrite a document. Published editor 0.8.0 provides guarded track resizing, block moves, creation/removal and explicit pagination with preview/undo. A general automatic repair loop, automatic weight allocation, complete payload-internal measurement, CLI explanations and a canvas **Auto arrange** preview/undo operation remain open work; the **Arrange** controls below are explicit human adjustments.\n\nWith `overflow: "warn"` (default), the result retains all text and returns diagnostics. SVG emits all lines and marks overflowing groups with `data-opf-overflow="true"`; text may extend beyond its box or canvas. Consumers can collect diagnostics using `onDiagnostic`. With `overflow: "error"`, the layout rejects content that does not fit. Shorten the affected content, give it more space, or explicitly split it into another slide. Use the explicit pagination transform below to produce additional editable slides.\n\nThe editor exposes `editor.composeSlide(index)` and `editor.setComposition(index, value)`. The latter validates the change, records JSON Patch history, and supports undo/redo.\n\n## Pagination\n\n```js\nimport { paginateSlide, paginatePresentation } from \'@openpresentation/opf/pagination\';\nconst { presentation, pages } = paginatePresentation(deck);\n// Review, save, render, or export `presentation`; pages maps output fragments to source paths.\nconst single = paginateSlide(deck.slides[0], { width: 1280, height: 720, minFontSize: 24 });\n```\n\nPagination is an authoring operation. It produces ordinary OPF slides; previews and PPTX export consume those exact pages. It preserves the input, body order, nested groups, promoted regions, rich-text formatting, and source text characters. Plain text and rich runs split at grapheme boundaries, preferring sentence/paragraph breaks and then word breaks. Lists split between items, tables between rows with column labels repeated, and code splits without rewriting its source. Indivisible payloads remain intact. Existing track weights continue to apply to positions on each resulting page.\n\nThe default readability target is 24 reference pixels. Core 0.8.0 returns slides that persist that floor in `composition.minFontSize`, including an already-fitting one-page result; existing higher minima and strict overflow policies remain intact. Quotes can raise their nominal body/footer sizes to the floor. Current core 0.11.0 enforces the selected floor in shared plain/rich/list/table fitting, including painted rich fragments, while retaining authored style metadata. Unsupported fits report overflow rather than silently capping output below the floor. The [readability-floor checkpoint](plans/readability-floor.md) records the earlier candidate and its bounded-search tradeoffs. Pagination relies on the shared engine\'s estimates; it is not a guarantee that every host font renders identically. Headings repeat unchanged, speaker notes remain on the first page, and continuation IDs avoid existing deck IDs. `pages[].mappings` records full source/output paths and half-open text or item ranges. Text offsets use UTF-16, so source strings can be reconstructed exactly. Quote bodies split at grapheme boundaries and repeat complete attribution/source fields on each page. An irreducible footer rejects the whole operation, including a quote with an empty body after earlier content.\n\nIf a heading, individual list item, table row, or other atomic payload cannot fit on an otherwise empty page, `OPFPaginationError` returns actionable diagnostics. There is no partial output. `maxSlides` defaults to 100, and a layout-evaluation limit bounds work on pathological input. Specialized chart and timeline internals still require visual inspection; their complete density models remain outstanding.\n\nThe editor\'s `editor.paginateSlide(index)` is one validated transaction with undo/redo. It returns `{change, pagination}`. Editor 0.5.0 commits a one-page readability-policy change too; repeating the operation after the policy is recorded returns `change: null`. The playground includes an overflowing draft and **Split overflow** action. The CLI writes a new file and refuses to overwrite an existing one:\n\n```sh\nopf paginate input.opf.json output.opf.json\n```\n\n## Fidelity boundary\n\nThe shared engine provides identical body and heading geometry to SVG and editable PPTX export. Text measurements default to deterministic estimates. For actual font advances, use the shared provider described in [measured fonts](font-fidelity.md). Complex scripts, fallback fonts, PowerPoint text rendering, rich text, charts, tables, and images still need visual verification. Dynamic composition is not a guarantee of pixel-identical PowerPoint output. List density includes rich runs, descriptions and nesting via `fitList`, with the same hanging indents used in preview and export. Only text-like payloads currently receive content-density estimates; small-cell diagnostics also cover non-text content.\n\nSVG embeds raster data URI images locally. Remote and file images require a host resolver that supplies a raster data URI; otherwise they appear as placeholders. `strictAssets` rejects unresolved images. The runtime never fetches them.\n\nSee [the complete example](../examples/technical/dynamic-composition.opf.json) and [local ecosystem verification](ecosystem-development.md).\n\n\n## Metric internals (published coordinated packages)\n\nPublished core 0.11.0 exports `layoutMetric(value, box, options)` from the root and `@openpresentation/opf/composition`. It accepts a finite number, string, or `{value, unit?, label?, description?, delta?, trend?}`. Renderer 0.9.0, editor 0.8.0 and PPTX 0.9.1 consume its shared geometry for composition, atomic pagination, preview, editing and export. Core 0.9.0 predates this API. The [primitive checkpoint](plans/shared-metric-layout.md) and [source integration checkpoint](plans/shared-metric-integration.md) record its development; current pins and remaining native/font gates are in the [compatibility matrix](compatibility-matrix.md).\n\nPass the allocated reference-pixel `box`, resolved heading/body `fonts`, `textMeasurement`, canvas `scale`, effective `minFontSize`, source `path` and optional `overflow: \'error\'`. `metric-flow-v1` returns separate value/unit/label/description/delta/trend parts in that order. Numeric zero is visible; scalar values keep the scalar path. Every provided field retains its original string or number in `sources[].value`. Source ranges address `String(value)` using UTF-16 offsets; the original spelling of a numeric JSON token is not available. No locale formatting, trend icon, case conversion or separator is invented. Empty optional strings retain source mappings with `visible: false`; the required empty value keeps a targetable blank line.\n\nThe allocator tries an adjacent value/unit baseline when both fit one line and the unit uses at most 35% of the cell width; otherwise it stacks the fields. Metadata has an eight-reference-pixel gap, with twelve pixels after the primary row. Related fields stay together rather than being separated by a percentage of the cell height. The value starts at up to 76 reference pixels (28% of cell height); label/unit/delta start at 23, description at 20 and trend at 18. Every requested size is raised to the chosen floor, scaled once. Natural metadata height gets space before reducing type. At most 48 arrangements are evaluated, each with at most 77 value-size trials; identical inputs and a deterministic measurement provider select the fitting candidate with least summed font reduction, preferring the first candidate on ties.\n\nEach part exposes requested/resolved styles and the same source-preserving line/segment representation used by code (`CodeTextFit`), measured with proportional heading/body fonts. CR/LF/CRLF, tabs, whitespace and grapheme boundaries remain exact. Consumers must reuse accepted line and segment positions, font sizes and styles rather than independently re-fit or normalize text. This API reports `provided` measurement when a provider is passed, without claiming that its glyph coverage or shaping is complete. Unsupported glyphs propagate the provider\'s error with the field path.\n\nPass `align: \'left\' | \'center\' | \'right\'` (default left) to the primitive. Its returned `alignment` and per-part `linePositions` give an absolute x origin and baseline for each `fit.sourceLines` entry, including blank lines. An inline value/unit pair moves together, with the gap following the actual value advance. Composition accepts host-resolved `contentAlignment`; an explicit slide `design.contentAlignment` overrides it. Renderer/export/pagination pass the effective design into the same operation. Alignment does not trigger a second font fit.\n\nCheck `overflow` before consuming parts. Irreducible text, invalid available space, parts outside the cell and overlapping occupied line boxes return field-specific diagnostics; strict mode throws `OPFCompositionError`. Invalid available boxes retain their dimensions and have no fit. These are advance-based line rectangles, not glyph outlines: the controlled browser evidence separately records small glyph overhangs. The API is a bounded internal allocator, not the complete layout-repair/Auto arrange operation or a native export fidelity guarantee.\n\nCurrent `grid-score-v9` retains the metric scoring introduced by `grid-score-v4`: every metric part contributes font reduction, with one overflow penalty per failing metric leaf. `item.metricLayout` is measured against the rounded accepted cell; `item.text`/`item.textStyle` alias the value fit/style. Field diagnostics obey strict ancestor policies, while explicit modes/weights/regions remain authoritative. Metrics are excluded from the advance-model `unmeasuredPayloads` list. Explicit pagination retains metrics atomically with complete source types/metadata and rejects irreducible fields without returning partial output. The published train has [installed-package acceptance](evidence/shipped-train-20260921/installed/acceptance-summary.json), including metric provenance controls. That evidence does not establish native raster or font equivalence.\n\n## Code internals (core 0.9.0 and coordinated packages)\n\nCore 0.9.0 introduced `layoutCode(value, box, options)` for the schema\'s string shorthand or `{source, language?, filename?}` object. It returns measured filename/language/body parts with exact original text, requested/resolved styles, readability floors, available boxes and diagnostics. Its original integration targeted renderer/PPTX 0.7.0 and editor 0.6.0; the [release checkpoint](plans/shared-code-release.md) records that rollout. These contracts are published in the current core 0.11.0, renderer 0.9.0, PPTX 0.9.1 and editor 0.8.0 train. Core 0.8.0 predates this API and retains the [recorded code-label, filename and whitespace defects](plans/layout-repair.md).\n\n`grid-score-v3` charges code font reductions across all metadata/body parts and one overflow penalty per failing leaf. It preserves explicit modes, weights, regions and source order. Accepted `item.codeLayout` is fitted to the same rounded cell exposed as `item.box`; `item.text` and `item.textStyle` alias the body, not the first metadata part. Strict ancestor settings apply to internal `.source`, `.filename` and `.language` diagnostics. Code no longer appears in `explanation.unmeasuredPayloads`, which concerns the core advance-based model only. It does not mean browser/native fidelity is verified.\n\nPagination slices the code body at grapheme boundaries, repeats filename/language and returns contiguous UTF-16 body ranges while preserving all source bytes and the evaluated readability floor. Irreducible metadata rejects all output, including when the body is empty or earlier content could have fitted. Consumers must preview/export the returned document. The [integration checkpoint](plans/shared-code-integration.md) separates source, installed browser, Windows PowerPoint and remaining release gates.\n\nEach fitted part retains every space and explicit CR/LF/CRLF break. `fit.lines` contains exact source slices, and `fit.sourceLines` records half-open UTF-16 `start`, `end` and `nextStart` offsets, the measured width and a `soft`, `hard` or `end` boundary. A hard break occupies `[end, nextStart)`; soft wrapping consumes no source character. Joining `part.text.slice(line.start, line.nextStart)` reconstructs the original part. Blank lines and a final empty line are retained, and long tokens split only at grapheme boundaries. Filename and language text are not case-converted. An absent/empty metadata pair creates a generated `code` label with no source range.\n\n`code-flow-v1` uses 18-reference-pixel outer insets, an eight-pixel gap between filename and language, and a twelve-pixel gap before the body. Nominal metadata/body sizes are 14/18 reference pixels, raised when necessary to respect the selected minimum, then scaled once. At most four metadata nominal/floor combinations are tried; each body fit tries at most 19 sizes regardless of canvas scale. The fitting combination with least font reduction wins. Irreducible metadata/body failures retain all text and diagnostic paths; invalid available boxes have no fit. `overflow: \'error\'` rejects rather than returning partial output.\n\nTabs remain literal characters in part text and displayed-line slices. Measurement advances to the next multiple of four measured spaces from that line\'s origin; `fit.tabSize` and `fit.tabWidth` expose the rule. Each source line\'s `segments` contains exact text/tab source ranges plus measured `x`/`width` values relative to its origin. Consumers must reuse those positions: an Edge probe showed that SVG treats a tab as one space despite CSS `tab-size: 4`. The published SVG renderer uses positioned spans and geometric precision; native export uses accepted tab stops. Width measurements and source preservation alone do not establish glyph-outline containment, shaping/bidi support or native fidelity. [Installed workflow evidence](evidence/shared-code-installed/summary.json) records the separate actual browser and native checks with their exact font/runtime scope.\n\nPublished native export stores source boundaries in standard PowerPoint shape tags. Complete unique groups recover exact code/source metadata, with current native text taking precedence. Missing, damaged or ambiguous groups retain visible native shapes and report diagnostics. Reimport does not reconstruct native formatting, positioning, font theme or readability policy. Eight installed-export wide/portrait slides pass native edit/save/reopen and all 24 original/saved/edited imports on the recorded Windows PowerPoint build; this is not arbitrary PowerPoint round-trip or pixel equivalence. The editor preserves untouched CRLF/CR source around edits and keeps committed preview geometry separate from its active native textarea caret.\n\nThe JSON schema can accept strings that [XML 1.0 cannot represent](https://www.w3.org/TR/xml/#charsets). Published SVG/PPTX code output rejects forbidden controls, unpaired UTF-16 surrogates, U+FFFE and U+FFFF with `invalid-code-text`, the source field path and UTF-16 offset in the message. The input stays unchanged; the caller can correct that character explicitly. Tabs, CR/LF/CRLF and valid supplementary characters remain accepted for serialization. Schema support, format representability and glyph coverage are separate properties.\n\nThe controlled SVG harness requests `text-rendering="geometricPrecision"` as well as explicit segment placement. Initial Linux Chromium CI rounded glyph advances under default hinting, unlike Windows Edge with the same font bytes. The [SVG specification](https://www.w3.org/TR/SVG/painting.html#TextRenderingProperty) defines geometric precision as a rendering hint, so consumers still need actual browser checks with their exact fonts and supported environments; the hint alone does not certify agreement. The harness retains a 0.1-reference-pixel tolerance and records observations before assertions.\n\n## Quote internals (core 0.8.0 and coordinated packages)\n\nCore 0.8.0 exports `layoutQuote(value, box, options)` from the root or composition entrypoint. Pass validated quote content (object or string shorthand), its allocated reference-pixel box, resolved `fonts`, `textMeasurement`, `scale` (canvas short edge / 720), effective `minFontSize`, `overflow` policy and its source `path`.\n\nThe result contains `parts` for the body and any nonempty footer, exact display `text`, source mappings, requested and resolved text styles, and the available boxes/fits. Source ranges use half-open UTF-16 offsets in both the source field and display string; generated quotation marks and the footer separator have no source range. The original content is never modified. A supplied width provider is reported as `provided`; it does not certify shaping or font fidelity.\n\nCheck `overflow` and `diagnostics` before accepting the parts. Invalid available dimensions remain visible with `fit` absent, and `overflow: \'error\'` throws `OPFCompositionError`. Diagnostics distinguish invalid part space, parts outside their cell, text that exceeds its reserved space, and overlapping line rectangles. Those rectangles are conservative text-layout bounds, not measured glyph outlines. The readability floor is scaled once and can raise the nominal body (28) or footer (17) size; it is never silently capped below the selected floor.\n\n`quote-flow-v1` keeps 18-reference-pixel outer insets and an 18-pixel body/footer gap while fonts scale with the canvas. A 40-pixel footer is a whitespace preference. The allocator expands it for long sources or compacts it for dense bodies, trying at most the nominal and minimum footer sizes and selecting the fitting pair with least total font reduction. If neither fits, it returns floor-size failure diagnostics. This is a bounded internal allocation step, not a complete layout-repair engine.\n\n`composeSlide` scores both parts and accepts geometry against the final rounded item box. Each quote item carries `quoteLayout`; its compatibility `text` field is the same fit object as the quote body, including generated quotation marks. Consumers needing original offsets must use the explicit `sources` mappings. The coordinated renderer and PPTX consume these parts without another measurement/style-resolution pass. Missing geometry or invalid part boxes reject rendering/export rather than omitting content. This requires core 0.8.0 with renderer/PPTX 0.6.0; older core 0.7.0/renderer 0.5.1/PPTX 0.5.2 lack these changes. The complete published set, immutable verification refs and fresh registry evidence are recorded in `release-plan.json` and [the release plan](plans/shared-quote-release.md).\n\nBrowser glyph bounds can extend slightly beyond advance-based part boxes into the reserved inset. Current loaded-font tests record those overhangs, verify glyph containment inside the full quote cell and check body/footer separation. Native PowerPoint fixtures separately verify text, sizes, cell containment, save/reopen and reimport. Neither test establishes universal pixel equivalence. Original requested-font provenance through host substitutions and non-quote payload internals remain open requirements.\n\n## Resizing in the preview\n\nChoose **Arrange** in the editor to reveal track dividers. Drag a divider to redistribute the space between adjacent columns (row/grid) or rows (column), including nested groups. Arrow keys make small changes; Shift makes larger changes. Escape discards a pointer draft. One drag creates one undo step, and no content is removed. Strict overflow rejects a resize that violates its fit constraints.\n\nResizing an automatic layout makes its chosen columns explicit as `mode: grid` with `columns`. This prevents the number of columns from changing under the pointer. The adjacent share clamps to 5\u201395%, with positive schema-valid weights. Other track proportions and unrelated document fields remain intact. Promoted regions retain their positions; their nested groups can still be resized. Layouts with reserved placeholder slots need an explicit arrangement first. Flows with more than twelve tracks need grouping before the current resize controls can express all weights.\n\n`createCanvasEditor(container, {layoutEditing: true, ...options})` enables dividers initially. `canvas.setLayoutEditing(boolean)` toggles them, and `canvas.commit()` / `canvas.cancel()` also handle an active resize. `onDraft` receives the proposed document; the session stays unchanged until commit. Changes to the resized container cancel a stale draft; unrelated updates are retained.\n\nThe shared engine exposes `geometry.flows`: each flow has its container path, content box, resolved column/row tracks (offset and size), clamped gap, effective composition, item count, and reserved slot count. This is renderer geometry, not new OPF document fields.\n\nAgents can prepare the same guarded change without a DOM:\n\n```js\nimport {prepareTrackResize} from \'@openpresentation/opf-editor/layout\';\nimport {resolvePresentation} from \'@openpresentation/opf-render/svg\';\nconst geometry = resolvePresentation(editor.document, renderOptions).slides[0].geometry;\nconst flow = geometry.flows.find(flow => flow.path === \'slides.0\');\nconst prepared = prepareTrackResize(editor.document, flow, 0, 0.65);\n// Boundary 0: give the first track 65% of the adjacent pair\'s combined space.\n// Preview prepared.document with the same renderer and font provider before applying.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n```\n\nThe patch contains a `test` guard for the container before changing its composition. Failed tests do not mutate the document or its history. A test-only patch is read-only. Rendering is preflighted by the canvas; headless callers should likewise render a candidate to enforce font and overflow constraints.\n\nVerification: editor layout model tests, `/layout-tests.html` browser keyboard checks and trusted-pointer specimens, and `pnpm test:layout` for measured SVG/native PPTX coordinate parity. Shape-coordinate checks do not establish PowerPoint raster pixel parity.\n\n\n## Reordering and moving blocks\n\nIn **Arrange**, drag a numbered block handle to reorder siblings. The insertion marker shows the destination; the shared renderer reflows the slide after drop. Arrow keys on a handle move the whole block earlier or later. Click a handle for **Earlier**, **Later**, or an explicit destination and insertion position. The destination menu supports existing groups and block-based slides, including moving a child out of a group or moving a whole group to another slide. `canvas.openBlockMenu(path)` opens the same controls programmatically.\n\nA move preserves the entire block and its nested content, formatting, data, and references. Parent composition weights describe positions, so they stay in place. Moving to another container can change the block\'s inherited design and readability constraints; the canvas renders the candidate before committing it. Strict overflow or an unavailable required font rejects the move. A move cannot leave an empty block container or put a group inside its own descendants. Move the group or add another block first when the source has only one child.\n\n```js\nimport {prepareBlockMove, listBlockContainers} from \'@openpresentation/opf-editor/layout\';\nconst containers = listBlockContainers(editor.document);\nconst prepared = prepareBlockMove(editor.document,\n \'/slides/0/blocks/0\', \'/slides/0/blocks/1\', 1);\n// Insert the first block before child 1 of the second block\'s group.\n// Destination indexes refer to the document before removal.\n// prepared.path reports the moved block\'s address after any index shifts.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n```\n\n`prepareBlockMove` returns `{document, patches, path, changed}`. It validates the complete result and emits guarded remove/add patches, so the editor or CLI can apply it atomically. No-op moves return `changed: false` and no patches. `listBlockContainers(document, {slideIndex})` optionally limits discovery to a single slide and excludes arbitrary extension data. Headless callers should render the candidate with their intended font provider before applying. The browser and installed-package block harnesses exercise nested moves, undo, stale menus, keyboard access, strict-fit rejection, and native drag reordering.\n\nCreation and deletion use the same layout engine: insertions can normalize implicit payloads into explicit blocks; deletions prune empty groups while retaining the slide. Existing track weights stay positional. See the [editor creation guide](live-editor.md#create-duplicate-and-delete-content) for the guarded APIs and canvas controls.\n'
44
50
  },
45
51
  {
46
52
  "slug": "ecosystem-development",
47
53
  "file": "docs/ecosystem-development.md",
48
54
  "title": "Local ecosystem development",
49
- "markdown": "# Local ecosystem development\n\nKeep `opf`, `opf-render`, `opf-pptx`, `opf-editor`, and `pptx-gallery` in the same parent directory. Install each repository's dependencies normally, then run these commands from `opf`:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm test:ecosystem\npnpm test:gallery\n```\n\nThe link command replaces the installed `@openpresentation/opf` package in sibling `node_modules` with a link to this checkout and builds the toolkit packages. It also links the renderer into editor/converter consumers and the converter into the editor. It does not save machine-specific paths in package manifests or lockfiles. Reinstalling dependencies can replace the links; rerun the command afterwards. Use `--packages-only` to omit the gallery checkout.\n\nOn Windows, directory junctions work without granting file-symlink privileges. The linker refuses a package parent that resolves outside the sibling checkout's `node_modules`, and replaces existing links without following them into source. npm/pnpm orchestration invokes the package manager's JavaScript entrypoint with the selected Node runtime instead of running a batch shim through a shell. Paths with spaces and shell metacharacters remain literal arguments. The supported npm-installed and npm-exec package-manager layouts are discovered from `PATH` or the matching `npm_execpath`; a missing manager returns an explicit installation error.\n\nThe core packed-install smoke check also uses this Windows invocation. Node 20/24 local evidence on the `codex/windows-test-harness-20260909` branch: all 414 core tests plus composition/pagination/data/rich-text/list suites pass, and actual local tarballs install into fresh temporary projects and pass 519 packed-entry checks. New isolated tests execute real npm builds, replace existing junctions, retain literal arguments, and reject an external `node_modules` parent without modifying its package. Windows/macOS CI repeats the core packed installation on both supported runtimes. These are local unpublished tarballs, not republished core 0.7.0 or proof of native rendering fidelity.\n\nAfter integrating reviewed layout PR #43, the combined source passes all 420 core tests on local Windows Node 24. Exact combined-source CI and review are recorded on PR #44.\n\nCoordinated CI `34384776504` and `34385059710` caught an older isolated-link fixture copying the linker without its new helper, causing `ERR_MODULE_NOT_FOUND` before package tests ran. The fixture now copies both files, passes directly on Windows Node 20/24, and runs in the Windows/macOS matrix as well as coordinated CI. This failure was fixed rather than waived; renewed combined-source CI remains required.\n\nThe published compatible set is core 0.7.0, CLI 0.5.0, renderer 0.5.1, PPTX 0.5.2 and editor 0.4.0. Clean registry installs include shared composition and styled table rows without sibling links. `release-plan.json` records exact versions and immutable verification sources; `pnpm test:registry-ecosystem` and `pnpm test:registry-fidelity` exercise those installed packages. Source links are for coordinated development.\n\nExecute the installed-package browser harnesses after their corresponding build:\n\n```sh\npnpm test:packages\npnpm test:packed-browser\npnpm test:registry-ecosystem\npnpm test:packed-browser registry\n```\n\nThe renderer checkout supplies its locked Playwright test dependency. Install Chromium with `npm exec --prefix ../opf-render -- playwright install --with-deps chromium` on Linux. Local Windows runs use Edge; `OPF_BROWSER_CHANNEL` can explicitly select another installed Playwright channel. CI uses the matching official Playwright container pinned by digest, without installing OS packages during each run.\n\nEach build writes `artifacts/editor/packed-browser-manifest.json` with the mode, installed versions, consumer build ID, dependency-lock hash and exact font/HTML/JavaScript hashes. The runner rejects a different mode, stale consumer or changed asset. It serves only the verified bytes on loopback and rejects external requests and network writes. Rebuild before switching between candidate and registry modes. Reports include the browser and Node versions and are saved by mode/runtime; failures retain a screenshot.\n\n`node scripts/test-packed-browser-guards.mjs` verifies those four rejection cases against the current disposable harness and restores each changed fixture byte-for-byte. CI runs it after the registry browser checks.\n\nSeven suites exercise canvas, rich text, lists, creation, layout, block moves and styled tables. Real browser input covers divider resizing/cancellation/concurrent changes, block dragging, merged-cell typing/redo/undo, plain-to-rich conversion and bold formatting, and empty-cell typing/undo. Conversion and formatting currently create separate undo transactions. Harness DOM assertions also cover renderer agreement and preservation. These checks do not replace full application export/reimport, public deployment checks or native PowerPoint raster evidence.\n\nTo browse the gallery with the linked package:\n\n```sh\ncd ../pptx-gallery\nOPF_LOCAL_WORKSPACE=1 pnpm dev\n```\n\nLayout detail pages have an interactive composition example. The flag expands Turbopack's local root to include the sibling package; production builds use the gallery root.\n\n`pnpm test:ecosystem` validates the dynamic composition fixture, edits and undoes a composition, renders SVG/PNG/PDF, exports editable PPTX, checks OOXML text-box coordinates against the shared geometry, and imports the result back into schema-valid OPF. Artifacts are written to a temporary directory and its location is printed.\n\nFor tests that should read current source without modifying installed packages, use Node's local loader after building OPF:\n\n```sh\nnode --import ./scripts/register-local-opf.mjs ../opf-render/test/smoke.mjs\n```\n\nThe loader redirects only `@openpresentation/opf` imports to this checkout. Ordinary dependencies still resolve from the consuming repository.\n\nFor full gallery render coverage, run `pnpm test:gallery -- --render` (or invoke the script with `--render`). The test validates all 854 generated documents and can render them with the local SVG engine.\n\nBuild OPF before starting a linked gallery. Stop and restart the gallery around clean OPF rebuilds; removing the linked `dist` directory during compilation can leave Turbopack with stale missing-module errors.\n\n`pnpm test:pagination` verifies long-text and table pagination through SVG and editable PPTX, including exact source reconstruction, table row counts, and absence of extra exporter-created pages. It writes review artifacts under `artifacts/pagination/`.\n\n`pnpm test:fonts` verifies actual-font measurement across editor, SVG, pagination, and PPTX. See [font fidelity](font-fidelity.md) for loading and embedding local fonts and for current native PowerPoint limits.\n"
55
+ "markdown": "# Local ecosystem development\n\nUse Node 24 for the current source and published packages. Keep `opf`, `opf-render`, `opf-pptx`, `opf-editor`, and `pptx-gallery` in the same parent directory. Install each repository's dependencies normally, then run these commands from `opf`:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm test:ecosystem\npnpm test:gallery\n```\n\nThe link command replaces the installed `@openpresentation/opf` package in sibling `node_modules` with a link to this checkout and builds the toolkit packages. It also links the renderer into editor/converter consumers and the converter into the editor. It does not save machine-specific paths in package manifests or lockfiles. Reinstalling dependencies can replace the links; rerun the command afterwards. Use `--packages-only` to omit the gallery checkout.\n\nOn Windows, directory junctions work without granting file-symlink privileges. The linker refuses a package parent that resolves outside the sibling checkout's `node_modules`, and replaces existing links without following them into source. npm/pnpm orchestration invokes the package manager's JavaScript entrypoint with the selected Node runtime instead of running a batch shim through a shell. Paths with spaces and shell metacharacters remain literal arguments. The supported npm-installed and npm-exec package-manager layouts are discovered from `PATH` or the matching `npm_execpath`; a missing manager returns an explicit installation error.\n\nThe core packed-install smoke check also uses this Windows invocation. The following portability results record the historical September 9 integration, before the current Node 24 requirement; current acceptance is linked from the [compatibility matrix](compatibility-matrix.md). Node 20/24 local evidence on the `codex/windows-test-harness-20260909` branch: all 414 core tests plus composition/pagination/data/rich-text/list suites pass, and actual local tarballs install into fresh temporary projects and pass 519 packed-entry checks. New isolated tests execute real npm builds, replace existing junctions, retain literal arguments, and reject an external `node_modules` parent without modifying its package. The then-current Windows/macOS CI repeated the core packed installation on both runtimes. These are local unpublished tarballs, not republished core 0.7.0 or proof of native rendering fidelity.\n\nAfter integrating reviewed layout PR #43, the combined source passes all 420 core tests on local Windows Node 24. Exact combined-source CI and review are recorded on PR #44.\n\nCoordinated CI `34384776504` and `34385059710` caught an older isolated-link fixture copying the linker without its new helper, causing `ERR_MODULE_NOT_FOUND` before package tests ran. The fixture now copies both files, passes directly on Windows Node 20/24, and runs in the Windows/macOS matrix as well as coordinated CI. This failure was fixed rather than waived; renewed combined-source CI was required at that checkpoint.\n\nThe current published compatible set is core 0.11.0, CLI 0.9.0, renderer 0.9.0, PPTX 0.9.1 and editor 0.8.0 on Node 24. Clean registry installs include shared composition and styled table rows without sibling links. `release-plan.json` records exact versions and immutable verification sources; `pnpm test:registry-ecosystem` and `pnpm test:registry-fidelity` exercise those installed packages. Source links are for coordinated development.\n\nExecute the installed-package browser harnesses after their corresponding build:\n\n```sh\npnpm test:packages\npnpm test:packed-browser\npnpm test:registry-ecosystem\npnpm test:packed-browser registry\n```\n\nThe renderer checkout supplies its locked Playwright test dependency. Install Chromium with `npm exec --prefix ../opf-render -- playwright install --with-deps chromium` on Linux. Local Windows runs use Edge; `OPF_BROWSER_CHANNEL` can explicitly select another installed Playwright channel. CI uses the matching official Playwright container pinned by digest, without installing OS packages during each run.\n\nEach build writes `artifacts/editor/packed-browser-manifest.json` with the mode, installed versions, consumer build ID, dependency-lock hash and exact font/HTML/JavaScript hashes. The runner rejects a different mode, stale consumer or changed asset. It serves only the verified bytes on loopback and rejects external requests and network writes. Rebuild before switching between candidate and registry modes. Reports include the browser and Node versions and are saved by mode/runtime; failures retain a screenshot.\n\n`node scripts/test-packed-browser-guards.mjs` verifies those four rejection cases against the current disposable harness and restores each changed fixture byte-for-byte. CI runs it after the registry browser checks.\n\nSeven suites exercise canvas, rich text, lists, creation, layout, block moves and styled tables. Real browser input covers divider resizing/cancellation/concurrent changes, block dragging, merged-cell typing/redo/undo, plain-to-rich conversion and bold formatting, and empty-cell typing/undo. Conversion and formatting currently create separate undo transactions. Harness DOM assertions also cover renderer agreement and preservation. These checks do not replace full application export/reimport, public deployment checks or native PowerPoint raster evidence.\n\nTo browse the gallery with the linked package:\n\n```sh\ncd ../pptx-gallery\nOPF_LOCAL_WORKSPACE=1 pnpm dev\n```\n\nLayout detail pages have an interactive composition example. The flag expands Turbopack's local root to include the sibling package; production builds use the gallery root.\n\n`pnpm test:ecosystem` validates the dynamic composition fixture, edits and undoes a composition, renders SVG/PNG/PDF, exports editable PPTX, checks OOXML text-box coordinates against the shared geometry, and imports the result back into schema-valid OPF. Artifacts are written to a temporary directory and its location is printed.\n\nFor tests that should read current source without modifying installed packages, use Node's local loader after building OPF:\n\n```sh\nnode --import ./scripts/register-local-opf.mjs ../opf-render/test/smoke.mjs\n```\n\nThe loader redirects only `@openpresentation/opf` imports to this checkout. Ordinary dependencies still resolve from the consuming repository.\n\nFor full gallery render coverage, run `pnpm test:gallery -- --render` (or invoke the script with `--render`). The test validates all 854 generated documents and can render them with the local SVG engine.\n\nBuild OPF before starting a linked gallery. Stop and restart the gallery around clean OPF rebuilds; removing the linked `dist` directory during compilation can leave Turbopack with stale missing-module errors.\n\n`pnpm test:pagination` verifies long-text and table pagination through SVG and editable PPTX, including exact source reconstruction, table row counts, and absence of extra exporter-created pages. It writes review artifacts under `artifacts/pagination/`.\n\n`pnpm test:fonts` verifies actual-font measurement across editor, SVG, pagination, and PPTX. See [font fidelity](font-fidelity.md) for loading and embedding local fonts and for current native PowerPoint limits.\n"
50
56
  },
51
57
  {
52
58
  "slug": "evidence-2026-09-08-windows",
@@ -58,13 +64,19 @@ var docsData = Object.freeze([
58
64
  "slug": "examples",
59
65
  "file": "docs/examples.md",
60
66
  "title": "OPF Examples Guide",
61
- "markdown": "# OPF Examples Guide\n\nThe `examples/` directory has three layers:\n\n- `examples/technical/` contains compact fixtures that isolate one or two schema behaviors.\n- `examples/gallery/` contains scenario-oriented decks that show OPF working across industries, functions, education, government, international, presentation-type, and design/media use cases.\n- The examples root is kept as an organizing directory rather than a home for standalone OPF files.\n\n## Technical Fixtures\n\nUse `examples/technical/` when you want a small file that exercises a specific schema surface:\n\n- content payloads, rich text, blocks, charts, tables, media, metrics, quotes, and timelines\n- promoted region keys and span combinations\n- asset string/object forms and asset-backed chart data\n- design backgrounds, logo sets, headers, footers, watermarks, and slide-level overrides\n- metadata array forms, language metadata, narrative beats, and catalog overrides\n\n## Gallery Folders\n\n| Folder | What It Demonstrates |\n| --- | --- |\n| `industries/` | Vertical market decks with operating plans, investment briefs, readiness reviews, and launch coordination. |\n| `business-functions/` | Department-specific decks for sales, marketing, product, engineering, finance, HR, legal, security, support, procurement, and strategy. |\n| `education/` | K-12, higher education, research, advising, workforce, advancement, and student services scenarios. |\n| `government/` | Public health, transit, emergency management, utilities, regulators, courts, parks, workforce, tax, and civic engagement decks. |\n| `presentation-types/` | Reusable deck archetypes such as pitches, board updates, QBRs, conference talks, workshops, postmortems, launches, policy briefings, training, and research reports. |\n| `international/` | Region- or language-specific decks, including examples of language object metadata and right-to-left direction. |\n| `design-and-media/` | Decks that emphasize design controls, image/video assets, data storytelling, and self-running orientation patterns. |\n\n## Patterns To Look For\n\n- Technical fixtures that isolate validator and renderer behavior.\n- Sparse gallery documents that use shorthand catalog references and a small slide list.\n- Medium documents with schema ids, metadata, organization and speaker records, design overrides, assets, and richer slide payloads.\n- Dense documents with inline `catalogs` sources and records, promoted region keys, `blocks`, media assets, code payloads, header/footer configuration, logo sets, watermarks, and extensions.\n- Mixed content payloads across text, bullets, lists, image, video, chart, table, code, metric, quote, and timeline slides.\n- Catalog references across narratives, layouts, chart types, themes, color schemes, font schemes, languages, audiences, purposes, tones, and social platforms.\n\n## Validation\n\nRun the example validator after changing any `*.opf.json` file:\n\n```sh\nnode scripts/validate-examples.mjs\n```\n\nThe script walks every OPF document under `examples/` and reports schema or semantic validation issues with file paths.\n"
67
+ "markdown": "# OPF Examples Guide\n\nThe `examples/` directory has two shipped layers, plus a docs fixture kept outside the catalog:\n\n- `examples/technical/` contains compact fixtures that isolate one or two schema behaviors.\n- `examples/gallery/` contains scenario-oriented decks that show OPF working across industries, functions, education, government, international, presentation-type, and design/media use cases.\n- The representative deck for [the published-package quickstart](quickstart.md) lives at [`docs/quickstart/developer-quickstart.opf.json`](quickstart/developer-quickstart.opf.json), outside the catalog, so `@openpresentation/opf/examples` stays at the published example count (currently 126 decks); the renderer golden corpus tracks that catalog on its own release cadence.\n- The examples root is kept as an organizing directory rather than a home for standalone OPF files.\n\n## Technical Fixtures\n\nUse `examples/technical/` when you want a small file that exercises a specific schema surface:\n\n- content payloads, rich text, blocks, charts, tables, media, metrics, quotes, and timelines\n- promoted region keys and span combinations\n- asset string/object forms and asset-backed chart data\n- design backgrounds, logo sets, headers, footers, watermarks, and slide-level overrides\n- metadata array forms, language metadata, narrative beats, and catalog overrides\n\n## Gallery Folders\n\n| Folder | What It Demonstrates |\n| --- | --- |\n| `industries/` | Vertical market decks with operating plans, investment briefs, readiness reviews, and launch coordination. |\n| `business-functions/` | Department-specific decks for sales, marketing, product, engineering, finance, HR, legal, security, support, procurement, and strategy. |\n| `education/` | K-12, higher education, research, advising, workforce, advancement, and student services scenarios. |\n| `government/` | Public health, transit, emergency management, utilities, regulators, courts, parks, workforce, tax, and civic engagement decks. |\n| `presentation-types/` | Reusable deck archetypes such as pitches, board updates, QBRs, conference talks, workshops, postmortems, launches, policy briefings, training, and research reports. |\n| `international/` | Region- or language-specific decks, including examples of language object metadata and right-to-left direction. |\n| `design-and-media/` | Decks that emphasize design controls, image/video assets, data storytelling, and self-running orientation patterns. |\n\n## Patterns To Look For\n\n- Technical fixtures that isolate validator and renderer behavior.\n- Sparse gallery documents that use shorthand catalog references and a small slide list.\n- Medium documents with schema ids, metadata, organization and speaker records, design overrides, assets, and richer slide payloads.\n- Dense documents with inline `catalogs` sources and records, promoted region keys, `blocks`, media assets, code payloads, header/footer configuration, logo sets, watermarks, and extensions.\n- Mixed content payloads across text, bullets, lists, image, video, chart, table, code, metric, quote, and timeline slides.\n- Catalog references across narratives, layouts, chart types, themes, color schemes, font schemes, languages, audiences, purposes, tones, and social platforms.\n\n## Validation\n\nRun the example validator after changing any `*.opf.json` file:\n\n```sh\nnode scripts/validate-examples.mjs\n```\n\nThe script walks every OPF document under `examples/` and reports schema or semantic validation issues with file paths.\n"
62
68
  },
63
69
  {
64
70
  "slug": "font-fidelity",
65
71
  "file": "docs/font-fidelity.md",
66
72
  "title": "Measured fonts and reproducible previews",
67
- "markdown": "# Measured fonts and reproducible previews\n\nFor the starter set and delivery priorities, see the [font roadmap](plans/font-roadmap.md).\n\nThe [Windows reference-font advance study](evidence/shared-metric-native-anchor/font-study-comparison.json) is exploratory source-checkpoint evidence, not an additional compatibility certification. It measures 1,024 cases across regular/bold Calibri, Arial, Times New Roman and Courier New, recording local reference-file versions/hashes and native font-slot names. Disabling optional ligatures and rounding base glyph advances to eighth-point steps predicts 949 observations within 0.02pt; 75 outliers remain, including combining marks, Arabic and Calibri kerning. Office theme tokens and per-glyph fallback are not resolved to exact native files by these name properties. No runtime provider or open-font mapping changes from this hypothesis, and no reference font is redistributed.\n\nThe composition API accepts a `textMeasurement` provider. A provider resolves font faces and returns actual text widths; callers pass the same provider to pagination, editor geometry, SVG rendering, and PPTX export. Without one, the existing deterministic character-width estimate remains available.\n\nRenderer 0.8.0 publishes `prepareNodeFonts` from `/fonts-node`. Its returned `options` combine the same registry measurement, embedded SVG fonts and explicit raster files, with system/bundled fallback disabled for raster calls. Pass these options to pagination, editor geometry, SVG, PPTX and PNG/PDF export. `pack: 'base'` is the default for authored Roboto decks; `pack: 'office'` adds the six Office substitute families and retains metric policy unless visual substitution is explicitly requested. `registry.substitutions` records actual substitutions; the helper does not rewrite the authored document or add native embedding.\n\nRenderer 0.8.0 groups static files by their OpenType preferred family while retaining legacy family names and explicit custom namespaces. `Roboto` requests at 500/600/800 now select the actual Medium/SemiBold/ExtraBold files instead of nearby 400/700 faces. Optional `TextStyle.fontFace` carries the physical legacy family and native bold/italic flags independently of CSS numeric weight: SemiBold/ExtraBold are regular within their legacy families. The converter consumes this metadata; providers without it retain their prior behavior. Nine actual base faces pass metadata/measurement/outline checks and offline Chromium advances on Node 20/24. Seven payload slides cover serialized native selectors, deterministic output and source/reimport. These checks do not establish native Office paint, embedding or broader script coverage; see the [Mac candidate evidence](evidence/mac-font-variants/README.md).\n\nRenderer 0.8.0 uses adjacent SVG spans when no measurement provider is supplied. This closes the visible gaps caused by estimated fragment widths while retaining estimated line breaks and all run text/style/source offsets. Supplied providers and accepted placements still use exact fragment origins. Measured SVG requests geometric precision; accepted outline placements also constrain horizontal advances with `textLength`/`spacingAndGlyphs` to avoid browser quantization drift. This can scale glyphs horizontally while retaining nominal font size and baseline. Width-only providers do not receive that constraint, and constrained widths do not certify raw font-metric equivalence. The compatible editor supports both forms. This is a spacing improvement, not evidence that unmeasured wrapping or glyph coverage is accurate; see [candidate evidence](evidence/mac-rich-flow/README.md).\n\nBoth Node loaders verify exact package versions, 33 font-file hashes and eight license-notice hashes against the immutable `BUNDLED_FONT_MANIFEST` exported from `/fonts-node`. Missing/modified resources reject with actionable errors. Default raster loading now includes all nine base faces instead of omitting Roboto semibold, italic and bold italic. Font files must remain available and unchanged between preparation and raster export. Browser loading, actual glyph coverage, variant naming, rich spacing, and native compatibility remain separate requirements. These APIs are available in [renderer 0.8.0](https://github.com/OpenPresentation/opf-render/releases/tag/opf-render-v0.8.0), published against core 0.10.0 with Node 24. Prepared HarfBuzz shaping and variable-instance work remain separate drafts.\n\nThe renderer's optional font registry uses [Fontkit](https://github.com/foliojs/fontkit) to shape text and measure glyph advances from local font bytes. It does not discover system fonts or fetch fonts. The Node helper loads the renderer's bundled Roboto and Roboto Mono faces:\n\n```js\nimport { loadBundledFontRegistry } from '@openpresentation/opf-render/fonts-node';\nimport { renderSvg, svgToPng } from '@openpresentation/opf-render';\nimport { paginatePresentation } from '@openpresentation/opf/pagination';\nimport { toPptx } from '@openpresentation/opf-pptx';\n\nconst registry = await loadBundledFontRegistry();\nconst options = { textMeasurement: registry.textMeasurement };\n// Use design.fontScheme: 'roboto', or supply the document's actual font files.\nconst { presentation } = paginatePresentation(deck, options);\nconst svg = renderSvg(presentation, {\n ...options,\n embeddedFonts: registry.embeddedFonts,\n});\nconst png = await svgToPng(svg, {\n fontFiles: registry.fontFiles,\n useBundledFonts: false,\n loadSystemFonts: false,\n});\nconst pptx = await toPptx(presentation, options);\n```\n\n`createFontRegistry` from `@openpresentation/opf-render/fonts` accepts `{data: Uint8Array, weight, italic?, family?, postscriptName?, license?}` entries in Node or the browser. Weights are explicit, with 400 as the default. Supply each style that the document uses. Missing font families and unsupported glyphs fail with `OPFFontError`, including the source path where available. Collection fonts require a `postscriptName` selecting one face.\n\nAliases and fallback families are explicit choices:\n\n```js\nconst registry = createFontRegistry(faces, {\n aliases: { Aptos: 'Roboto', 'Aptos Display': 'Roboto' },\n fallbackFamily: 'Roboto',\n});\nconsole.log(registry.substitutions);\nregistry.clearSubstitutions(); // Start a fresh render's diagnostic collection.\n```\n\nAn available exact family takes precedence over aliases. The registry resolves a requested weight to the closest supplied weight, reports the substitution, and makes the resolved style available to rendering. Missing italic/upright styles fail instead of synthesizing an unmeasured style. `strictGlyphs: false` is an explicit escape hatch for hosts with their own glyph-fallback policy; it is unsuitable for fidelity verification.\n\nSVG embeds supplied fonts using data URIs and includes supplied license notices as metadata. The bundled loader carries the fonts' SIL Open Font License notices. For PNG/PDF, pass the same font files to the rasterizer; its native font loader does not depend on browser CSS font loading. In a browser, wait for `document.fonts.ready` before measuring or taking a screenshot. The editor playground loads and embeds bundled fonts and displays substitutions.\n\nCurrent `svgToPdf` output is image-only: each slide is rasterized and embedded as a PNG on a PDF page. Text is not selectable or searchable through PDF text objects, and shapes are not preserved as vectors. The accepted [selectable/vector PDF roadmap](plans/pdf-export.md) adds a separate backend and verification requirement, including permitted font embedding, Unicode extraction and shared placement. Raster PDF will remain an explicit compatibility mode when the verified vector mode becomes the default; no vector mode has shipped yet.\n\n## Office compatibility pack\n\n`loadOfficeFontRegistry` from `@openpresentation/opf-render/fonts-node` supplies regular, bold, italic, and bold italic faces of Carlito, Caladea, Arimo, Tinos, Cousine, and Gelasio, plus the base Roboto pack. Package versions are pinned and each face carries its distribution's license notice. `includeBaseFonts: false` omits Roboto. Loading never installs fonts into the operating system or downloads fonts at render time.\n\n```js\nconst registry = await loadOfficeFontRegistry({\n substitutionPolicy: 'metric', // Default for this loader; no visual fallback.\n});\nregistry.resolveFont({fontFamily: 'Calibri', fontWeight: 400});\n// requestedFamily: Calibri, resolvedFamily: Carlito, compatibility: metric\n```\n\n`createFontRegistry` defaults to `substitutionPolicy: 'none'`. Policies are `none`, `metric`, and `visual`; visual permits both curated tiers. An explicit `fallbackFamily` is a separate, reported `generic` fallback. Aliases are explicit visual substitutions and never establish metric compatibility. `resolveFont` reports exact resolutions as well; `substitutions` only collects changes. Resolution records include requested/resolved weights, italic, source path, and supporting upstream information where available.\n\n| Requested family | Bundled substitute | Current automatic tier |\n| --- | --- | --- |\n| Calibri | Carlito | Metric intent, standard 400/700 styles |\n| Cambria | Caladea | Fontconfig metric mapping; reference-version testing remains necessary |\n| Arial | Arimo | Metric, standard 400/700 styles |\n| Times New Roman | Tinos | Metric, standard 400/700 styles |\n| Courier New | Cousine | Metric, standard 400/700 styles |\n| Georgia | Gelasio | Visual: optional ligatures changed measured widths |\n| Calibri Light | Carlito | Visual: the bundle has no Carlito Light face |\n| Aptos / Aptos Display | Carlito, unless Source Sans 3 is supplied | Visual; no Aptos metric claim |\n\nUpstream evidence: [Carlito](https://github.com/googlefonts/carlito), [Fontconfig mappings](https://chromium.googlesource.com/external/fontconfig/+/refs/heads/main/conf.d/30-metric-aliases.conf), [Arimo](https://github.com/google/fonts/blob/main/ofl/arimo/DESCRIPTION.en_us.html), [Tinos](https://github.com/google/fonts/blob/main/ofl/tinos/DESCRIPTION.en_us.html), [Cousine](https://github.com/google/fonts/blob/main/apache/cousine/DESCRIPTION.en_us.html), and [Gelasio](https://github.com/SorkinType/Gelasio). Metric classification describes compatibility intent within the stated style scope, not universal identical output. Missing matching weights cannot silently qualify for the metric tier.\n\nThe exported `FONT_COMPATIBILITY` list also contains optional visual candidates and CJK families. Listing a candidate does not bundle it or imply complete character coverage. Liberation Sans Narrow is a separate legacy distribution with a different license history; it is not part of this bundle. Wingdings, Webdings, and Symbol require character mapping before substitution; an ordinary fallback fails with `font-encoding-required`. Missing math fonts require an explicit math-aware choice and fail with `math-font-required` instead of falling through to body text.\n\nDrawingML tokens such as `+mn-lt` resolve through the registry's explicit `themeFonts` option before substitution. Supply concrete `majorLatin`, `minorLatin`, and, where used, `majorEastAsia`, `minorEastAsia`, `majorComplexScript`, or `minorComplexScript` families. Missing theme mappings fail. This helper does not yet extract theme font records or embedded fonts from imported PPTX files.\n\n### Measured results and experimental fonts\n\n`node --import ./scripts/register-local-opf.mjs scripts/test-office-fonts.mjs --system` compares the bundle to reference fonts already installed in macOS's Supplemental directory. It does not redistribute reference fonts. The report records source-file hashes and individual shaped widths. Across four samples and four styles, Arimo/Arial, Tinos/Times New Roman, and Cousine/Courier New matched exactly on 48 runs. Gelasio/Georgia differed on ligature-containing runs, with a maximum difference of 2.0125%. Individual basic-Latin advances matched; disabling optional ligatures removed the tested difference. Until feature handling is consistent across outputs, the policy conservatively labels Gelasio approximate. Calibri and Cambria reference fonts were not available for this comparison.\n\n[Akasia](https://codeberg.org/bloudraad/akasia) remains experimental after the [v0.0.2 open-file assessment](evidence/akasia-assessment/README.md). All twelve styles match public upstream advance/kerning/ligature data, but 14 reference codepoints are missing and Black Italic decomposed accents expose a 0.421875px Fontkit/Chromium advance difference at size 32. Both Node runtimes retain the failure. This is not independent Aptos-binary or native Office verification; no mapping or bundled pack changes. `EXPERIMENTAL_FONT_CANDIDATES` records it separately. Aptos Narrow and Display remain outside that evidence.\n\nAn original OPF font project is technically feasible: independently designed or suitably open-licensed glyph outlines can be fitted to target advance widths, placement, vertical metrics, and shaping behavior. A successful font needs a reproducible source build, provenance, style/coverage tests, visual review, and cross-renderer conformance. Matching bounding boxes alone is insufficient: [OpenType horizontal metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx) and [glyph positioning](https://learn.microsoft.com/en-us/typography/opentype/spec/gpos) jointly control text placement. Universal pixel identity across rasterizers is not the acceptance criterion; measured layout preservation over an explicit test matrix is.\n\n## Verification and remaining work\n\n`pnpm test:fonts` checks that editor and SVG geometry match, every native PPTX text box has the same coordinates and measured line breaks, and export uses the resolved family. It writes artifacts to `artifacts/fonts/`. A real-browser check of the same Roboto run measured 324.032 pixels versus the font engine's 324.170 pixels at 25 pixels, a difference of 0.138 pixels. These are measured tolerances, not a promise of pixel identity.\n\nPPTX currently records the resolved font family; it does not embed font binaries. PowerPoint still needs those fonts installed or may substitute them. Line height remains the shared 1.22 multiplier, rather than a complete ascent/descent model. Rich-text font overrides, mixed-script fallback and bidi layout, specialized payload internals, and native font embedding remain active fidelity work. Passing a width provider does not remove those limits.\n"
73
+ "markdown": "# Measured fonts and reproducible previews\n\nFor the starter set and delivery priorities, see the [font roadmap](plans/font-roadmap.md).\n\n## Font policy (FF-31)\n\nOPF keeps one machine-readable font policy table, [`spec/reference/font-policy.json`](../spec/reference/font-policy.json). Core exports it as `FONT_POLICY`, `fontPolicyFor()` and `applyFontPolicyDecisions()` from `@openpresentation/opf` or `@openpresentation/opf/font-policy`. Each row gives a family's license class, where viewers get it, whether OPF may ever embed it, and its preview replacement with a measured width difference. It also lists alternates, ending where possible with a face that already ships with opf-render. The [licensing table](programs/font-fidelity-everywhere/font-licensing.md) lists all 153 rows. [`font-policy.schema.json`](../spec/reference/font-policy.schema.json) is its JSON Schema. The [measurement evidence](evidence/font-replacements-20260923/README.md) explains how each replacement was chosen.\n\n**The policy in brief (owner decisions, 2026-09-29).** This is the canonical statement; the sibling repositories link here.\n\n1. **The user's font selection is the source of truth.** The user picks a font, for example Aptos or Calibri through a font scheme. That name is what the document, the theme and the PPTX carry.\n2. **License-restricted (proprietary) fonts are never bundled or embedded,** so previews cannot draw them. Openly licensed fonts such as Carlito and Roboto are bundled, and \"licensed\" below always means license-restricted. Live previews, SVG, the editor and gallery thumbnails use an open look-alike instead. The goal is a replacement that looks similar and is metric-compatible (same advance widths and line metrics), so text occupies the same size on screen and wraps as it does in PowerPoint. Calibri\u2192Carlito is the model.\n3. **Where no metric-compatible open replacement exists yet (Aptos today),** a visual-only look-alike is a documented fallback. It is reported as visual and is a known layout-fidelity gap to close, not the intended end state. The parity scoreboard counts it as near, not perfect.\n4. **PPTX export always writes the selected font name,** for example `typeface=\"Aptos\"` in the theme and in runs, never the replacement. PowerPoint then opens the file and shows the actual font, installed or as a Microsoft 365 cloud font.\n5. **Only open fonts may be embedded,** through the explicit embed path (FF-13).\n\n**Release status.** Point 4 is true on opf-pptx `main`. The published opf-pptx 0.9.1 still writes the substitute into the PPTX, and so do the editor and pptx.gallery builds that depend on it; selected-name export reaches them with the next opf-pptx release.\n\n**Provisional owner decisions (provisional, owner may revise).** Three choices about which open face stands in for a family are pending with the owner. The policy above is settled; only these replacement picks are provisional. Root resolved them provisionally with the recommended defaults:\n\n| Decision | Families | Replacement |\n| --- | --- | --- |\n| `aptos-preview` | Aptos (the default `aptos` scheme) | Roboto (visual) |\n| `segoe-ui-preview` | Segoe UI, Semibold, Light and Semilight | Red Hat Display (visual) |\n| `cambria-tier` | Cambria | Caladea, reclassified from metric to visual. Its `metricModeFallback` keeps metric-mode registries previewing Cambria with Caladea, reported as visual, as they did before FF-31. |\n\nAll three live in one block, `provisionalDecisions`, at the top of the JSON. The rows that follow a decision carry no replacement family of their own. A change of decision is therefore a one-line edit. When a decision changes, a stored measurement of the old family is dropped as unmeasured until `scripts/measure-font-replacements.mjs` is run again.\n\n1. **Licensed, non-free fonts are never bundled or embedded.** This covers Aptos, Calibri, Cambria, Segoe UI, Georgia, Tahoma, Grandview, Seaford, Tenorite, Consolas, Times New Roman, Arial, Courier New and the Windows script fonts. Rendering uses the family's designated open replacement instead:\n - **Metric-compatible** is the goal, used where a replacement exists and measures identical: Calibri\u2192Carlito, Arial\u2192Arimo, Times New Roman\u2192Tinos and Courier New\u2192Cousine. A metric row needs an upstream statement and a measurement in all four styles, with a mean width difference below 0.1% and no corpus string more than 0.3% off. Georgia\u2192Gelasio fails that test: ligature runs differ by up to 1.02% as opf-render shapes them. It is therefore visual, even though every basic-Latin advance matches.\n - **Alternates** are tried, in order, when the declared replacement's pack is not loaded. An alternate is always reported as visual, including on a metric row.\n - **Otherwise the closest measured open face**, marked visual: a documented fallback and a known layout-fidelity gap until a metric-compatible replacement exists. For example, Aptos\u2192Roboto measures a 2.15% mean width difference.\n2. **The PPTX always names the chosen family, and no font file is included.** The exporter writes `Aptos` when the document chose Aptos. PowerPoint resolves standard fonts on the viewer's machine: those shipped with Office, Windows or macOS, and Microsoft 365 cloud fonts. The replacement never reaches the package (opf-pptx `test/export-chosen-fonts.mjs`).\n3. **Openly licensed fonts render as themselves.** Examples are Roboto, Carlito and the Noto script families. They can reach a PPTX only through an explicit embed path (FF-13), never by default.\n4. **Families that are proprietary and non-standard, or missing from the table,** keep their name in the PPTX. `fontAvailabilityDiagnostics()` reports that viewers may lack them. It also flags Microsoft 365 cloud-only fonts (such as Aptos) and families that ship only in an optional Windows language feature.\n\n### Which faces are available\n\nopf-render ships only fonts that it already pins and hash-verifies:\n- **Base pack:** Roboto and Roboto Mono.\n- **Office pack:** Carlito, Caladea, Arimo, Tinos, Cousine and Gelasio.\n- **Optional `scripts` pack (FF-19):** Noto for non-Latin scripts and CJK.\n\nSome replacements are open families that no renderer pack ships yet, such as Red Hat Display for Segoe UI and Red Hat Text for Tahoma. For these, the renderer tries the declared replacement first, then the alternates. The last alternate is the best measured bundled face, so previews stay deterministic without any extra download. `registry.substitutions` records which face was used and its tier. The proposed `catalog` pack (21 OFL `@expo-google-fonts` packages, listed in the opf-render PR) would make the declared replacements and the open catalog families available. Downloading it needs approval, and it is not part of this change.\n\n| Environment | Open catalog families | Proprietary families | When the real font is required |\n| --- | --- | --- | --- |\n| Local Node, cloud or serverless (`prepareNodeFonts({pack: 'office', substitutionPolicy: 'visual'})`) | Exact when a pack ships them (Roboto, Carlito, \u2026, Noto with `scripts`) | Metric replacement: identical widths. Visual replacement: approximate, reported in `registry.substitutions` with the measured delta | Supply licensed files with `prepareNodeFonts({faces: [{path, family, weight, italic}]})`; they resolve as exact faces |\n| Strict mode (`substitutionPolicy: 'metric'`) | Exact | Metric replacements only | `font-unavailable` names the license class, the declared replacement and tier, the pack, and the caller hook. There is never a silent wrong-metric fallback |\n| Browser (`loadBrowserFontRegistry`) | Exact when the host serves the pack files | Same replacement rules | Same hook: pass the caller's own faces |\n| PowerPoint (exported PPTX) | Named; the viewer needs the font or substitutes | The selected name, never the replacement: the real font on Office, Windows or macOS, or through Microsoft 365 cloud fonts | Named; the viewer substitutes |\n\n**Aptos, the default scheme (replacement pick provisional, owner may revise).** Aptos is a Microsoft 365 cloud font and is not redistributable. The recommended cloud default previews Aptos with Roboto, the closest measured open face with all four styles, already in the base pack: mean width difference 2.15%, signed +0.1%, maximum 7.4% on a single string. Aptos Display previews with Carlito (1.8%). The exported PPTX still names Aptos and Aptos Display. Deployments that hold an Aptos license can pass the real files through `faces` for exact previews.\n\nThe pptx.gallery parity scoreboard's `fontResolution` check counts a family as perfect only when it is the real face or a metric-compatible replacement. The owner decided on 2026-09-29 that a declared, measured visual replacement such as Aptos\u2192Roboto is acceptable as a fallback when the PPTX keeps the selected name. It is classified near, not perfect, so the default `aptos` scheme stays short of perfect there until a metric-compatible Aptos replacement exists or licensed Aptos faces are supplied. The harness change merged in [opf#159](https://github.com/OpenPresentation/opf/pull/159). See the [program decision](programs/font-fidelity-everywhere/README.md#decisions).\n\nThe [Windows reference-font advance study](evidence/shared-metric-native-anchor/font-study-comparison.json) is exploratory source-checkpoint evidence, not an additional compatibility certification. It measures 1,024 cases across regular/bold Calibri, Arial, Times New Roman and Courier New, recording local reference-file versions/hashes and native font-slot names. Disabling optional ligatures and rounding base glyph advances to eighth-point steps predicts 949 observations within 0.02pt; 75 outliers remain, including combining marks, Arabic and Calibri kerning. Office theme tokens and per-glyph fallback are not resolved to exact native files by these name properties. No runtime provider or open-font mapping changes from this hypothesis, and no reference font is redistributed.\n\nThe composition API accepts a `textMeasurement` provider. A provider resolves font faces and returns actual text widths; callers pass the same provider to pagination, editor geometry, SVG rendering, and PPTX export. Without one, the existing deterministic character-width estimate remains available.\n\nRenderer 0.8.0 publishes `prepareNodeFonts` from `/fonts-node`. Its returned `options` combine the same registry measurement, embedded SVG fonts and explicit raster files, with system/bundled fallback disabled for raster calls. Pass these options to pagination, editor geometry, SVG, PPTX and PNG/PDF export. `pack: 'base'` is the default for authored Roboto decks; `pack: 'office'` adds the six Office substitute families and retains metric policy unless visual substitution is explicitly requested. `registry.substitutions` records actual substitutions; the helper does not rewrite the authored document or add native embedding.\n\nRenderer 0.8.0 groups static files by their OpenType preferred family while retaining legacy family names and explicit custom namespaces. `Roboto` requests at 500/600/800 now select the actual Medium/SemiBold/ExtraBold files instead of nearby 400/700 faces. Optional `TextStyle.fontFace` carries the physical legacy family and native bold/italic flags independently of CSS numeric weight: SemiBold/ExtraBold are regular within their legacy families. The converter consumes this metadata; providers without it retain their prior behavior. Nine actual base faces pass metadata/measurement/outline checks and offline Chromium advances on Node 20/24. Seven payload slides cover serialized native selectors, deterministic output and source/reimport. These checks do not establish native Office paint, embedding or broader script coverage; see the [Mac candidate evidence](evidence/mac-font-variants/README.md).\n\nRenderer 0.8.0 uses adjacent SVG spans when no measurement provider is supplied. This closes the visible gaps caused by estimated fragment widths while retaining estimated line breaks and all run text/style/source offsets. Supplied providers and accepted placements still use exact fragment origins. Measured SVG requests geometric precision; accepted outline placements also constrain horizontal advances with `textLength`/`spacingAndGlyphs` to avoid browser quantization drift. This can scale glyphs horizontally while retaining nominal font size and baseline. Width-only providers do not receive that constraint, and constrained widths do not certify raw font-metric equivalence. The compatible editor supports both forms. This is a spacing improvement, not evidence that unmeasured wrapping or glyph coverage is accurate; see [candidate evidence](evidence/mac-rich-flow/README.md).\n\nBoth Node loaders verify exact package versions, 33 font-file hashes and eight license-notice hashes against the immutable `BUNDLED_FONT_MANIFEST` exported from `/fonts-node`. Missing/modified resources reject with actionable errors. Default raster loading now includes all nine base faces instead of omitting Roboto semibold, italic and bold italic. Font files must remain available and unchanged between preparation and raster export. Browser loading, actual glyph coverage, variant naming, rich spacing, and native compatibility remain separate requirements. These APIs are available in [renderer 0.8.0](https://github.com/OpenPresentation/opf-render/releases/tag/opf-render-v0.8.0), published against core 0.10.0 with Node 24. Prepared HarfBuzz shaping and variable-instance work remain separate drafts.\n\nThe renderer's optional font registry uses [Fontkit](https://github.com/foliojs/fontkit) to shape text and measure glyph advances from local font bytes. It does not discover system fonts or fetch fonts. The Node helper loads the renderer's bundled Roboto and Roboto Mono faces:\n\n```js\nimport { loadBundledFontRegistry } from '@openpresentation/opf-render/fonts-node';\nimport { renderSvg, svgToPng } from '@openpresentation/opf-render';\nimport { paginatePresentation } from '@openpresentation/opf/pagination';\nimport { toPptx } from '@openpresentation/opf-pptx';\n\nconst registry = await loadBundledFontRegistry();\nconst options = { textMeasurement: registry.textMeasurement };\n// Use design.fontScheme: 'roboto', or supply the document's actual font files.\nconst { presentation } = paginatePresentation(deck, options);\nconst svg = renderSvg(presentation, {\n ...options,\n embeddedFonts: registry.embeddedFonts,\n});\nconst png = await svgToPng(svg, {\n fontFiles: registry.fontFiles,\n useBundledFonts: false,\n loadSystemFonts: false,\n});\nconst pptx = await toPptx(presentation, options);\n```\n\n`createFontRegistry` from `@openpresentation/opf-render/fonts` accepts `{data: Uint8Array, weight, italic?, family?, postscriptName?, license?}` entries in Node or the browser. Weights are explicit, with 400 as the default. Supply each style that the document uses. Missing font families and unsupported glyphs fail with `OPFFontError`, including the source path where available. Collection fonts require a `postscriptName` selecting one face.\n\nAliases and fallback families are explicit choices:\n\n```js\nconst registry = createFontRegistry(faces, {\n aliases: { Aptos: 'Roboto', 'Aptos Display': 'Roboto' },\n fallbackFamily: 'Roboto',\n});\nconsole.log(registry.substitutions);\nregistry.clearSubstitutions(); // Start a fresh render's diagnostic collection.\n```\n\nAn available exact family takes precedence over aliases. The registry resolves a requested weight to the closest supplied weight, reports the substitution, and makes the resolved style available to rendering. Missing italic/upright styles fail instead of synthesizing an unmeasured style. `strictGlyphs: false` is an explicit escape hatch for hosts with their own glyph-fallback policy; it is unsuitable for fidelity verification.\n\nSVG embeds supplied fonts using data URIs and includes supplied license notices as metadata. The bundled loader carries the fonts' SIL Open Font License notices. For PNG/PDF, pass the same font files to the rasterizer; its native font loader does not depend on browser CSS font loading. In a browser, wait for `document.fonts.ready` before measuring or taking a screenshot. The editor playground loads and embeds bundled fonts and displays substitutions.\n\nCurrent `svgToPdf` output is image-only: each slide is rasterized and embedded as a PNG on a PDF page. Text is not selectable or searchable through PDF text objects, and shapes are not preserved as vectors. The accepted [selectable/vector PDF roadmap](plans/pdf-export.md) adds a separate backend and verification requirement, including permitted font embedding, Unicode extraction and shared placement. Raster PDF will remain an explicit compatibility mode when the verified vector mode becomes the default; no vector mode has shipped yet.\n\n## Office compatibility pack\n\n`loadOfficeFontRegistry` from `@openpresentation/opf-render/fonts-node` supplies regular, bold, italic, and bold italic faces of Carlito, Caladea, Arimo, Tinos, Cousine, and Gelasio, plus the base Roboto pack. Package versions are pinned and each face carries its distribution's license notice. `includeBaseFonts: false` omits Roboto. Loading never installs fonts into the operating system or downloads fonts at render time.\n\n```js\nconst registry = await loadOfficeFontRegistry({\n substitutionPolicy: 'metric', // Default for this loader; no visual fallback.\n});\nregistry.resolveFont({fontFamily: 'Calibri', fontWeight: 400});\n// requestedFamily: Calibri, resolvedFamily: Carlito, compatibility: metric\n```\n\n`createFontRegistry` defaults to `substitutionPolicy: 'none'`. Policies are `none`, `metric`, and `visual`; visual permits both curated tiers. An explicit `fallbackFamily` is a separate, reported `generic` fallback. Aliases are explicit visual substitutions and never establish metric compatibility. `resolveFont` reports exact resolutions as well; `substitutions` only collects changes. Resolution records include requested/resolved weights, italic, source path, and supporting upstream information where available.\n\n| Requested family | Bundled substitute | Current automatic tier |\n| --- | --- | --- |\n| Calibri | Carlito | Metric intent, standard 400/700 styles |\n| Cambria | Caladea | Visual: advances differ from Cambria 6.99 by a mean of 2.7% (FF-31 measurement). Metric-mode registries still use it, reported as visual (`metricModeFallback`) |\n| Arial | Arimo | Metric, standard 400/700 styles |\n| Times New Roman | Tinos | Metric, standard 400/700 styles |\n| Courier New | Cousine | Metric, standard 400/700 styles |\n| Georgia | Gelasio | Visual: basic-Latin advances identical, but ligature runs differ by up to 1.02% as opf-render shapes them |\n| Calibri Light | Carlito | Visual: the bundle has no Carlito Light face |\n| Aptos / Aptos Display | Roboto / Carlito (FF-31 policy) | Visual; no Aptos metric claim |\n\nUpstream evidence: [Carlito](https://github.com/googlefonts/carlito), [Fontconfig mappings](https://chromium.googlesource.com/external/fontconfig/+/refs/heads/main/conf.d/30-metric-aliases.conf), [Arimo](https://github.com/google/fonts/blob/main/ofl/arimo/DESCRIPTION.en_us.html), [Tinos](https://github.com/google/fonts/blob/main/ofl/tinos/DESCRIPTION.en_us.html), [Cousine](https://github.com/google/fonts/blob/main/apache/cousine/DESCRIPTION.en_us.html), and [Gelasio](https://github.com/SorkinType/Gelasio). Metric classification describes compatibility intent within the stated style scope, not universal identical output. Missing matching weights cannot silently qualify for the metric tier.\n\nThe exported `FONT_COMPATIBILITY` list also contains optional visual candidates and CJK families. Listing a candidate does not bundle it or imply complete character coverage. Liberation Sans Narrow is a separate legacy distribution with a different license history; it is not part of this bundle. Wingdings, Webdings, and Symbol require character mapping before substitution; an ordinary fallback fails with `font-encoding-required`. Missing math fonts require an explicit math-aware choice and fail with `math-font-required` instead of falling through to body text.\n\nDrawingML tokens such as `+mn-lt` resolve through the registry's explicit `themeFonts` option before substitution. Supply concrete `majorLatin`, `minorLatin`, and, where used, `majorEastAsia`, `minorEastAsia`, `majorComplexScript`, or `minorComplexScript` families. Missing theme mappings fail. This helper does not yet extract theme font records or embedded fonts from imported PPTX files.\n\n### Measured results and experimental fonts\n\n`node --import ./scripts/register-local-opf.mjs scripts/test-office-fonts.mjs --system` compares the bundle to reference fonts already installed in macOS's Supplemental directory. It does not redistribute reference fonts. The report records source-file hashes and individual shaped widths. Across four samples and four styles, Arimo/Arial, Tinos/Times New Roman, and Cousine/Courier New matched exactly on 48 runs. Gelasio/Georgia differed on ligature-containing runs, with a maximum difference of 2.0125%. Individual basic-Latin advances matched; disabling optional ligatures removed the tested difference. Until feature handling is consistent across outputs, the policy conservatively labels Gelasio approximate. Calibri and Cambria reference fonts were not available for this comparison.\n\n[Akasia](https://codeberg.org/bloudraad/akasia) remains experimental after the [v0.0.2 open-file assessment](evidence/akasia-assessment/README.md). All twelve styles match public upstream advance/kerning/ligature data, but 14 reference codepoints are missing and Black Italic decomposed accents expose a 0.421875px Fontkit/Chromium advance difference at size 32. Both Node runtimes retain the failure. This is not independent Aptos-binary or native Office verification; no mapping or bundled pack changes. `EXPERIMENTAL_FONT_CANDIDATES` records it separately. Aptos Narrow and Display remain outside that evidence.\n\nAn original OPF font project is technically feasible: independently designed or suitably open-licensed glyph outlines can be fitted to target advance widths, placement, vertical metrics, and shaping behavior. A successful font needs a reproducible source build, provenance, style/coverage tests, visual review, and cross-renderer conformance. Matching bounding boxes alone is insufficient: [OpenType horizontal metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx) and [glyph positioning](https://learn.microsoft.com/en-us/typography/opentype/spec/gpos) jointly control text placement. Universal pixel identity across rasterizers is not the acceptance criterion; measured layout preservation over an explicit test matrix is.\n\n## Verification and remaining work\n\n`pnpm test:fonts` checks that editor and SVG geometry match and that every native PPTX text box has the same coordinates and measured line breaks. With opf-pptx FF-31 (opf-pptx#63), export names the chosen family, not the preview substitute. It writes artifacts to `artifacts/fonts/`. A real-browser check of the same Roboto run measured 324.032 pixels versus the font engine's 324.170 pixels at 25 pixels, a difference of 0.138 pixels. These are measured tolerances, not a promise of pixel identity.\n\nPPTX records the chosen font family (FF-31); it never records a preview replacement and never embeds a proprietary font binary. PowerPoint still needs those fonts installed, through Office, the OS or Microsoft 365 cloud fonts, or it substitutes them. Line height remains the shared 1.22 multiplier, rather than a complete ascent/descent model. Rich-text font overrides, mixed-script fallback and bidi layout, specialized payload internals, and native font embedding remain active fidelity work. Passing a width provider does not remove those limits.\n"
74
+ },
75
+ {
76
+ "slug": "format-card",
77
+ "file": "docs/format-card.md",
78
+ "title": "OPF format card",
79
+ "markdown": '# OPF format card\n\nA self-contained authoring reference for agents and humans writing `*.opf.json` documents, sized for pasting into a model\'s context. The canonical contract is the JSON Schema (`https://openpresentation.org/schema/opf/v1`, in [`spec/schemas/opf.schema.json`](../spec/schemas/opf.schema.json)); this card compresses it. Validate with `validatePresentation` from `@openpresentation/opf` or `opf validate <file>`.\n\n## Document shape\n\nA presentation is one JSON object; only `slides` is required.\n\n```json\n{ "name": "Minimal Deck", "slides": [{ "title": "Minimal Deck" }] }\n```\n\nOptional top-level fields, grouped:\n\n- Identity: `name`, `description`, `filename`, `author`, `organization`, `speaker`, `tags`.\n- Intent (used by AI generation): `audience`, `purpose`, `tone`, `language`, `narrative`, `takeaway`, `duration`.\n- Appearance: `design` (`theme`, `colorScheme`, `fontScheme`, `dimensions`, `background`, `logo`, `watermark`, `header`, `footer`, alignment hints), `variables`.\n- Resources: `assets` (registry referenced as `asset:<id>`), `catalogs` (inline records and/or custom sources per kind).\n- Machine state: `extensions` (preserved, never rendered).\n\n## Slides and content\n\nA slide carries `title` / `subtitle` / `tag` / `notes` / `section` / `beat` / `layout` / `id` / `extensions` plus content in one of three shapes \u2014 pick the loosest that says what you mean:\n\n1. **Root payload** \u2014 content-kind fields directly on the slide. The kind is inferred from the field: `text`, `items` (list), `bullets`, `image`, `video`, `chart`, `table`, `code`, `metric`, `quote`, `timeline`. Several kinds at the root are shorthand for `blocks`.\n2. **`blocks`** \u2014 an ordered array of payloads when placement should stay engine-inferred. A block is a leaf payload or a **group**: `{ "type": "group", "blocks": [...], "composition": { ... } }`. Groups nest (hard cap 32; stay \u2264 3 in practice) and cannot mix `blocks` with leaf fields.\n3. **Promoted regions** \u2014 a 3\xD73 grid when position matters. Rows `top|middle|bottom`, columns `left|center|right`, spans with `+`, intersections with `:` \u2014 `"left"`, `"center+right"`, `"top:left"`, `"middle+bottom:center+right"`. Keys must not overlap; regions cannot mix with a root payload.\n\n`composition` (on a slide or group) arranges children: `{ "mode": "auto|grid|row|column", "columns": 1-12, "weights": [..], "gap": 0-0.1, "padding": 0-0.2, "minFontSize": 8-32, "overflow": "warn|error" }`. Weights are relative track sizes; gap/padding are fractions of the canvas short edge. Engines own placement \u2014 there is no x/y.\n\nPayload notes: chart is `{ "type": "<chart-type id>", "data": { "columns": [...], "rows": [...] } }` or `{ "data": { "src": "asset:<id>" } }`; table rows may hold scalars, `TextRun[]`, or styled cells `{ "value", "style": { "fill", "color", "align", "borders", ... }, "colSpan", "rowSpan" }`; `metric`/`quote`/`code` accept string shorthand; list nesting uses `level` on items, not nested payloads.\n\n## Rich text and color references\n\n`text`, list items, bullets, and table cells accept `TextRun[]`: strings or `{ "text", "bold", "italic", "underline", "strikethrough", "color", "fontSize", "fontFamily", "link", "superscript", "subscript" }`.\n\nEvery content color field (`TextRun.color`, table cell `style.fill` / `style.color`, cell border `color`) accepts three forms:\n\n- Literal hex: `"#0F172A"`, `"#B42318CC"`.\n- A color-scheme name, resolved through the effective scheme: slots `accent1`\u2013`accent6`, `dark1`, `dark2`, `light1`, `light2`, `hyperlink`, `followedHyperlink`, or roles `primary`, `secondary`, `accent`, `background`, `surface`, `text`, `textSecondary`.\n- A variable reference `var:<id>` into the top-level map: `"variables": { "risk": "#B42318" }` (or `{ "type": "color", "value": "#B42318", "description": "..." }`).\n\nPrefer names and variables over hex \u2014 they survive re-theming. Unknown `var:` ids warn, never error. The styled cell and border color fields enforce the three forms at the schema level; run colors tolerate any string (unrecognized values warn and renderers fall back to the theme), so imported decks keep validating.\n\n## Ids and extensions\n\n`id` is optional on slides and on any content payload (including groups), unique document-wide. Use ids when something outside the document must address content across edits \u2014 patch edits, comments, review state. `extensions` objects at document, slide, and payload scope carry machine state; engines ignore and preserve them.\n\n## Catalog references\n\nReusable vocabulary lives in catalogs; references are kebab-case ids: `narrative`, `tone`, `purpose`, `audience`, `language`, `design.theme`, `design.colorScheme`, `design.fontScheme`, `Slide.layout`, `Chart.type`, socials keys. Resolution: inline `catalogs.<kind>.records[]` \u2192 `catalogs.<kind>.source` \u2192 bundled default catalog (browsable at https://pptx.gallery). Object form overrides a record per key: `{ "id": "cool-horizon", "accent1": "#0F4C81" }`. Unknown ids warn, never error. `opf bundle <in> <out>` inlines every record a document uses so it needs no catalog lookups beyond itself (remote media and data assets are separate).\n\n## Design in three lines\n\n`design.theme` bundles scheme + fonts + background + dimensions; `design.colorScheme` / `fontScheme` override it; `slides[].design` overrides per slide; resolution is per field, most specific wins. Backgrounds accept slot names (`"light1"`) or hex. `false` suppresses inherited `watermark` / `header` / `footer`.\n\n## Prefer / never\n\nPrefer: string shorthands; bare catalog ids; inference over explicit `type`; regions only when position matters; groups only when `blocks` ordering is not enough; names/`var:` over hex in color fields; `items` for lists (`bullets` only for plain text-style bullets).\n\nNever: loose chart or table fields directly on a slide; region keys mixed with a root payload; overlapping region keys; `blocks` mixed with leaf fields on one payload; duplicate ids; x/y or pixel geometry (it does not exist in OPF).\n\n## Two canonical slides\n\n```json\n{\n "id": "headline",\n "title": "Adoption Doubled",\n "left": { "id": "kpi", "metric": { "value": "2.1x", "label": "Adoption", "trend": "up" } },\n "center+right": { "chart": { "type": "line", "data": { "columns": ["Month", "Teams"], "rows": [["Jan", 12], ["Feb", 18]] } } }\n}\n```\n\n```json\n{\n "id": "risks",\n "title": "What Could Go Wrong",\n "composition": { "mode": "column" },\n "blocks": [\n { "items": [["Two regions at ", { "text": "85% utilization", "color": "var:risk", "bold": true }]] },\n { "quote": { "text": "Exceptions became visible before they became escalations.", "attribution": "VP Operations" } }\n ],\n "extensions": { "authoring": { "locked": true } }\n}\n```\n'
68
80
  },
69
81
  {
70
82
  "slug": "handoff-2026-09-08",
@@ -84,47 +96,83 @@ var docsData = Object.freeze([
84
96
  "title": "OpenPresentation owner handoff \u2014 September 15, 2026",
85
97
  "markdown": "# OpenPresentation owner handoff \u2014 September 15, 2026\n\n## Outcome and boundaries\n\nThe published websites demonstrate editable OPF JSON and live slides. Reusable\npackages provide an open local authoring foundation for scoped developer\nintegrations today. This is not full PowerPoint parity or a finished all-feature\npresentation editor.\n\nThe owner requested committed, remotely preserved work, validated merges or\nexplicit roadmap deferral, and a completed PR cleanup. Eleven dependency PRs\nand four shared-furniture PRs were accepted. One Node26-types update was closed\nto retain Node24. Four remaining shaping PRs conclude as docs/evidence only;\ntheir complete runtime prototypes remain on remote archives. No failed font\nimplementation is promoted, no history is force-pushed, and archives must stay.\n\nUse the original PRs listed below for final merge/check receipts. This document\nis committed in core83 before its final checks/merge and does not invent its\nown future merge SHA. Merging a roadmap does not release its archived feature.\n\n## Repositories and environment\n\n| Repository | Default | Responsibility |\n| --- | --- | --- |\n| [OpenPresentation/opf](https://github.com/OpenPresentation/opf) | main | Schemas, catalogs, composition/edit/lint, CLI, six skills and plans |\n| [OpenPresentation/opf-render](https://github.com/OpenPresentation/opf-render) | main | Font preparation, rendering and image/PDF output |\n| [OpenPresentation/opf-editor](https://github.com/OpenPresentation/opf-editor) | main | Reusable editor controls and source-preserving interaction |\n| [OpenPresentation/opf-pptx](https://github.com/OpenPresentation/opf-pptx) | main | Editable PPTX import/export and provenance |\n| [Data-Advantage/openpresentation-site](https://github.com/Data-Advantage/openpresentation-site) | main | Main website/playground |\n| [Data-Advantage/pptx-gallery](https://github.com/Data-Advantage/pptx-gallery) | main | Gallery and embedded editor demo |\n| [Data-Advantage/pptx-dev](https://github.com/Data-Advantage/pptx-dev) | master | Authoring/inspector/tooling website |\n\nUse Node24. Core/site/gallery pin pnpm10.33.2; pptx-dev pins pnpm11.1.3;\nstandalone libraries use npm lockfiles. Read each AGENTS.md, use codex/ branches\nand focused commits/PRs as work proceeds. Gallery requires Conventional Commits\nand its documented coauthor. Read relevant OPF skills for document operations.\n\nLocal clones live under /Users/michael/Source. This handoff is portable:\nGitHub contains archives and committed evidence. Do not depend on temporary\nworktrees, local dependency symlinks, untracked output or this Mac's credentials.\n\n## Published baseline and accepted source\n\nRegistry inspection confirmed @openpresentation/opf **0.10.0**, opf-render\n**0.8.0**, opf-editor **0.7.0**, opf-pptx **0.8.0**, and cli **0.8.0**.\nHome/playground support JSON editing and preview edits in both directions,\ncontextual catalog choices, indentation, paired quotes and other code-editor\nassistance. Shared controls and contextual lint/design contracts are available.\n\nThe CLI supports create, validate, lint, revision-guarded JSON edits, data\nimport, pagination, schema/catalog lookup and skill installation/update/status.\nDo not invent CLI render/export commands: use documented library/UI APIs.\nSchema-property access is not proof of full WYSIWYG interaction coverage.\n\nCore79 (d0c8841), renderer20 (130a2fa), editor17 (cfdeae6) and PPTX34 (c077b7a)\nadd accepted shared header/footer geometry, editing and provenance. Their main\nCI passed, including coordinated packages and Linux/Windows PPTX checks. These\nnew APIs need a new coordinated release. Do not claim they are in the listed\nnpm versions just because main contains them.\n\nKeep release-plan.json and immutable verification references accurate. Never\noverwrite published versions, change snapshots to conceal regressions or count\nsibling-source imports as installed-package checks. Some older docs/skills\nstill call released APIs unreleased: audit them with the developer quickstart.\n\n## Deployment receipts\n\nLatest retained canonical production checks cover **41 workflows**:\n\n| Website | Verified source commit | Workflows |\n| --- | --- | --- |\n| [openpresentation.org](https://www.openpresentation.org) | bb6b006e5fa18f565779781ad347a839fea4ec83 | 26 |\n| [pptx.gallery](https://www.pptx.gallery) | fe395e4e7187cdb85c2b8a4041ef2801fd81f72d | 5 |\n| [pptx.dev](https://www.pptx.dev) | 3b17ff5366f7a556cf02aae320e6baf87fdf3678 | 10 |\n\nDeployment records and browser logs are in\n[PR consolidation evidence](evidence/pr-consolidation-20260915/README.md).\nThey establish exercised flows, not every capability. The Zod4.6.2 preview\nfailure is fixed/merged in pptx-dev37 using explicit record keys/values and\npublic schema identification.\n\n## Preserved unfinished implementation\n\nEvery library repository retains remote branch codex/archive-shaping-20260915:\n\n| Repository / original PR | Complete immutable prototype |\n| --- | --- |\n| [opf83](https://github.com/OpenPresentation/opf/pull/83) | 36ff66b3d62b39d7d27dcda022b7e79e541bd603 |\n| [renderer21](https://github.com/OpenPresentation/opf-render/pull/21) | 343fb84223f4383ffe546157c6989ccc505c0acb |\n| [editor21](https://github.com/OpenPresentation/opf-editor/pull/21) | ae4cc6426b04c7ca428c1b4acacea2e11d99fa84 |\n| [PPTX35](https://github.com/OpenPresentation/opf-pptx/pull/35) | fbe9a73d012dbd51d65a39251e405e488651a70b |\n\nThese contain HarfBuzz shaping, variable/CFF preparation, prepared SVG glyphs,\nrich-source groups, grapheme/caret/selection/navigation work and native rich/tab\nexport experiments, with tests, licenses and evidence. See\n[the deferred implementation plan](plans/deferred-shaping-20260915.md).\nResume from main and port bounded changes; do not merge an archive wholesale.\n\nRenderer Linux still fails the unchanged 0.1px gate: Source Serif SmText Bold\nmeasures 334.06213682353496px versus Chromium334.193115234375px. Rounding fixes\nfive Linux rows but breaks five macOS rows. Some native platform widths differ\nby more than twice the tolerance. Define an honest supported metric/painting\ncontract; offsets, platform guesses and relaxed assertions do not solve it.\nTrack [renderer24](https://github.com/OpenPresentation/opf-render/issues/24).\n\nArchived editor CI separately has a packed-test mismatch: thirteen rich-input\nworkflows pass but the aggregate expects ten. Repair that assertion and rerun\nthe complete packed command when resuming; the archived run is not green.\n\n[Core87](https://github.com/OpenPresentation/opf/issues/87) and the\n[native plan](plans/powerpoint-acceptance.md) retain image opening, current-content\nprovenance after edit/save/reopen, tab tolerances, notes-master ordering and\nphysical font identity/embedding. Browser, serialization and self-import checks\ndo not establish Office acceptance. Windows host recovery is not authorized:\ndo not retry COM or kill Office processes until recovery is confirmed.\nAptos4.40 is restricted; do not use/distribute it without compatible permission.\n\n## Next milestones\n\nFollow [the developer adoption roadmap](plans/developer-adoption-20260915.md).\nFirst release the accepted increment and provide a clean installed example with\ncurrent API/version docs. Then finish public-site integration in\n[core88](https://github.com/OpenPresentation/opf/issues/88), preserving appearance.\nPackage adoption and feature adoption need separate checks.\n\nBroader work: supported multilingual fonts/fallback/IME/bidi and editing;\nnative Office evidence; bounded deterministic layout repair for dense/nested\ncontent; full visual-editor interaction coverage. Current PDF is raster-backed.\nSelectable vector PDF with legal embedding/Unicode extraction and general SVG/\nsemantic Mermaid follow font reliability. See the linked plans for acceptance.\n\nKeep the foundation provider-neutral and useful offline without an account or\nmodel call. Preserve content, whitespace, formatting, reading order, authored\nintent and undo. Bound layout repair and return actionable failure; never delete\ncontent to fit. AI may be optional application wiring, not a required embedded\ndesign agent. The [broad ecosystem objective](plans/ecosystem-objective-2026-09-09.md)\nremains open. The Codex goal was paused, not completed or replaced by this cleanup.\n\n## Audit record\n\n[Final evidence](evidence/final-handoff-20260915/README.md) records registry\nversions, remote archive checks, all seven repository/worktree inventories and\nlatest archived CI failures. Source audit found no uncommitted source, stashes\nor local-only branch commits. A stale untracked dependency symlink was removed\nwithout changing its target. A legacy temporary directory lacking Git metadata\nwas not treated as an active checkout.\n\nThe four concluding PRs restore runtime/tests/locks/CI to validated main, then\nadd docs/evidence. Verify their final diffs and CI and merge them before reporting\nqueue completion. Do not confuse preserved history with newly accepted code.\n"
86
98
  },
99
+ {
100
+ "slug": "handoff-2026-09-16",
101
+ "file": "docs/handoff-2026-09-16.md",
102
+ "title": "Handoff \u2014 September 16, 2026",
103
+ "markdown": '# Handoff \u2014 September 16, 2026\n\n> Historical 16\u201317 September checkpoint, superseded by the [21 September handoff](handoff-2026-09-21.md) and [current compatibility matrix](compatibility-matrix.md). The 0.10.1 train below is not the current install recommendation.\n\nContinue as primary owner. Read this file after the\n[15 September handoff](handoff-2026-09-15.md); that checkpoint\u2019s npm versions\nare outdated. Updated 17 September 2026 with production issue88 deploys. The\ndeveloper-ready milestone is **not** complete.\n\n## Merged and released\n\n- Coordinated packages on npm: core **0.10.1**, renderer **0.8.1**, editor **0.7.1**, PPTX **0.8.1**, CLI **0.8.1** (Node 24).\n- Shared header/footer geometry (`furniture-flow-v2`) is in that published set. Do not cut another npm release for missing furniture APIs.\n- [PR 91](https://github.com/OpenPresentation/opf/pull/91) was **squashed and merged** to `main` as [`0d47a14`](https://github.com/OpenPresentation/opf/commit/0d47a14316deea53b6be4bf2c2f759b4dd8ac739). `docs/quickstart.md` and `docs/compatibility-matrix.md` are on main, with fixture `docs/quickstart/developer-quickstart.opf.json` (outside the published 126-deck catalog) and `pnpm test:developer-quickstart` (fresh npm registry install, no `file:` links).\n- Post-merge independent registry re-run on Node 24 passed (author, lint, offline fonts, `furniture-flow-v2` composition, pagination, undo, SVG/PNG/raster PDF, PPTX). Main OPF CI for that merge also passed, including the registry quickstart step.\n\n## Production site adoption (2026-09-17)\n\nIssue88 **features below are live** on canonical custom domains. GitHub\n[issue 88](https://github.com/OpenPresentation/opf/issues/88) is still **open**\n(full checklist not satisfied). Do not describe geometry drafts or\nhomepage-renderer as shipped.\n\n| PR | Squash SHA | Vercel production deploy | Alias |\n| --- | --- | --- | --- |\n| [pptx-dev#42](https://github.com/Data-Advantage/pptx-dev/pull/42) | `17de6da9a909630475459f441da20601cd1cbcaa` on `master` | `dpl_6QHXPJ5QLTYXHNqtPoByqxHxbqHx` | `www.pptx.dev` |\n| [pptx-gallery#32](https://github.com/Data-Advantage/pptx-gallery/pull/32) | `c7d375461f236384c6837a2d2b6914fd2635ad38` on `main` | `dpl_2SyhP4oX23ZBjsnVec7WKKaSF8xU` | `www.pptx.gallery` |\n| [openpresentation-site#39](https://github.com/Data-Advantage/openpresentation-site/pull/39) | `ea0d0321a20856ed110ab43acc20cd231378d604` on `main` | `dpl_GqqrJyxmwT5JCeQY3m9Tw2SDRN5R` | `www.openpresentation.org` |\n\nVerified on those hosts (not `*.vercel.app`):\n\n- https://www.pptx.dev/inspector \u2014 overlay + preview-to-JSON + last-valid preview; Author json-options at `/author`. `/playground` **308** \u2192 `/inspector`.\n- https://www.pptx.gallery/layouts \u2014 Playground + Editor links outside card `<Link>`; **0** nested anchors.\n- https://www.openpresentation.org/playground \u2014 **Header & footer** example. Apex **308** \u2192 `www`.\n\nUnforced click on stacked Inspector `[data-opf-renderer]` SVG is still intercepted by the overlay. That leftover does not undo the production pass above.\n\n## PPTX furniture vs native Office Header/Footer\n\nPublished PPTX 0.8.1 exports furniture as **ordinary slide shapes** tagged `OPF_FURNITURE_V1` (plus `ppt/tags/opfFurniture*.xml` / `opfFurnitureSlide*.xml` manifests). That is **not** native PowerPoint Header/Footer objects (`p:hf` placeholders with content, notes-master headers/footers). A PptxGenJS slide master still emits a disabled default `<p:hf sldNum="0" hdr="0" ftr="0" dt="0"/>`; that is not furniture. Compiling shared headers/footers into real Office Header/Footer objects is [issue 87](https://github.com/OpenPresentation/opf/issues/87) / roadmap only. Do not implement native `p:hf` / notes-master furniture as part of the remaining developer-ready package work.\n\n## Not done\n\n- The developer-ready milestone. An empty PR queue does not finish it.\n- [Issue 88](https://github.com/OpenPresentation/opf/issues/88) GitHub issue: still **open**. Production features above are live; remaining checklist includes homepage `/` baseline, docs/stale API audit, overlay stacked-click intercept.\n- [Issue 87](https://github.com/OpenPresentation/opf/issues/87): native PowerPoint, including real Office Header/Footer objects (`p:hf`). Still open. Do not implement `p:hf` here.\n- [Renderer issue 24](https://github.com/OpenPresentation/opf-render/issues/24): Linux vs Chromium native-width residual at the **unchanged 0.1px gate**. Deferred. Do not weaken the gate, round away the residual, or rewrite goldens to hide it.\n- Geometry / homepage-renderer drafts are **not** shipped ([opf#94](https://github.com/OpenPresentation/opf/pull/94) and siblings; [openpresentation-site#40](https://github.com/Data-Advantage/openpresentation-site/pull/40)).\n- Archived shaping implementations remain on `codex/archive-shaping-20260915`; do not merge wholesale.\n- Selectable vector PDF, general SVG/Mermaid, full editor/IME/repair coverage.\n- Published tarball READMEs/docs still call furniture unpublished in places; treat that as stale copy, not API absence.\n\n## Next\n\n1. Keep the registry quickstart green; do not publish a new npm set for this docs checkpoint.\n2. Leave GitHub issue 88 open until its checklist is fully satisfied. Do not merge unrelated geometry or homepage-renderer drafts as if they were this work.\n3. Leave 24 and 87 explicit. Do not mark this milestone complete. Do not implement `p:hf`.\n'
104
+ },
105
+ {
106
+ "slug": "handoff-2026-09-21",
107
+ "file": "docs/handoff-2026-09-21.md",
108
+ "title": "OpenPresentation handoff \u2014 September 21, 2026",
109
+ "markdown": "# OpenPresentation handoff \u2014 September 21, 2026\n\nThe shipped ColorRef train is core **0.11.0**, CLI/renderer **0.9.0**,\nPPTX **0.9.1** and editor **0.8.0**, on **Node 24**. Shared furniture is already\npublished. Issue88, the developer-ready milestone and the broad ecosystem goal\nremain open. The [quickstart](quickstart.md), [compatibility matrix](compatibility-matrix.md)\nand release-plan.json describe this train; older dated handoffs are historical.\nThe [current checkpoint](evidence/font-readiness-acceptance-20260921/README.md),\n[prior Inspector actions checkpoint](evidence/inspector-current-actions-20260921/README.md),\n[earlier Inspector checkpoint](evidence/inspector-share-acceptance-20260921/README.md),\n[App53 checkpoint](evidence/author-source-acceptance-20260921/README.md),\n[completion checkpoint](evidence/completion-acceptance-20260921/README.md) and\n[earlier evidence ledger](evidence/issue88-final-20260921/README.md) separate\nreviewed source, CI, deployed behavior and unresolved acceptance work.\n\n## Current source and production\n\nAll seven primary local checkouts were clean and fast-forwarded from GitHub\nbefore this work. The [initial sync receipt](evidence/shipped-train-20260921/repository-sync.json)\nrecords that starting state. The [final seven-repository sync](evidence/font-readiness-acceptance-20260921/week-wrap/primary-week-wrap-sync.json)\nrecords all primary checkouts clean at the accepted refs below. No local branches\nor archived prototypes were deleted.\n\n| Repository | Accepted source baseline | Current status |\n| --- | --- | --- |\n| opf | `3c5048522714365a41d9b5b9ba81620affae718b` | Through PR111; required PR checks passed. Core111 postmerge runs were started at the final notice, not recorded as passed. Native evidence remains bounded; no new npm train |\n| opf-render | `c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b` | PR30 source merged; exact-head CI passed and the Windows supervisor reports postmerge CI passed. Published renderer remains 0.9.0; native compatibility remains incomplete |\n| opf-pptx | `9e8a38198519b4d4a18d163928833f2509550687` | Through PR49; PR48 tab and PR49 font harnesses merged with first-attempt pre/post CI. Their source acceptance does not broaden the bounded native results. Published PPTX remains 0.9.1 |\n| opf-editor | `476191e28e6f5f5ec32146aeb416f5286b4d0570` | PR26/27; editor0.8.0 |\n| openpresentation-site | `a85bcc77d899ce9ba1df659548be564142c16120` | PR43/44; current guides and downloads verified on canonical production |\n| pptx-dev | `8f54228a9e38a1dcc0bf8188bcdd519795b3799a` | App54 merged with the identical reviewed tree; primary clean. Premerge Linux/Windows pass 704 units, 13 standalone controls and 39/39 browsers. Exact READY production is live, but canonical gate fails (**34/39 passed**); postmerge Linux 39/39 passes while Windows 38/39 fails at initial font readiness. App53 and original App54 failures remain preserved |\n| pptx-gallery | `f17e9ae5869669d5fbac3720f285652d0c37551c` | PR35/36; current copy and canonical docs-to-editor flow verified |\n\nCore PR93/97/98/99/100/102/103 passed their pre-merge and post-merge CI gates, including the\napplicable OPF, CLI platform and coordinated installed-package/browser checks.\nCore102's reviewed head `e746dee147ec4d3d0eef69e22e7e1d3f48b6346a` and\naccepted main `578bcc6e0894129b00059258bd4ad1994a414baa` share tree\n`2de778c56a9d4afcc686830a39b881d147a1f4ee`. OPF and coordinated-package\npre-merge runs **35635885648 / 35635885561** and post-merge runs\n**35636929112 / 35636929090** passed on their first attempts under Node24.20.0.\nThe [compact core102 receipts](evidence/inspector-share-acceptance-20260921/README.md#accepted-core102)\nretain exact run/job outcomes and primary-clean/tree-equality evidence; the\n[core100 receipt](evidence/author-source-acceptance-20260921/core100/release-receipt.json)\nremains historical. This documentation checkpoint publishes no npm package and\nchanges no visual baseline. The core0.11.0 and\nCLI0.9.0 tags still point to `b8a1faf24203635bd36b09ea19f44bcd53781f16`.\n\n[Core104](https://github.com/OpenPresentation/opf/pull/104) accepted the held-app\ncheckpoint at `3d301f1bef2c5d55e7e2a58f1aa53632d9ec8ab1`, matching its reviewed tree.\nExact postmerge OPF CI **35643637673 passed**, while coordinated run\n**35643638022 was canceled** during installed-tarball browser checks. It is\nincomplete, not green. The workflow's concurrency configuration and timing\nsupport supersession by the next main push, without an identified cancellation actor.\n\n[Core105](https://github.com/OpenPresentation/opf/pull/105), accepted at\n`84e914710520a7b0e777fce30e5758ee64a64924`, directly descends from core104 and\npreserves all 60 reviewed core104 blobs. Exact-head OPF **35644048902** and\ncoordinated **35644048907** passed on their first attempts under Node 24.20.0.\nThis is descendant integration acceptance, not a rewrite of the canceled run.\nIts [native B/C bundle](evidence/windows-native-edits-20260921/README.md)\npasses offline inventory verification: 516 hashed files and 56 PPTX CRC/XML/font-program\nchecks, with no Office calls by this audit. [Compact release receipts](evidence/inspector-current-actions-20260921/README.md#core-source-and-ci)\nretain exact outcomes and source identities. PPTX47's Linux/Windows pre/post CI\nalso passed at `8c7908e7`; no npm versions changed.\n\n[Core106](https://github.com/OpenPresentation/opf/pull/106) is accepted at\n`3847f712ccb2379952bcc8ab7c9fdbaedfd0a4ce`. Its [tab/Carlito evidence](evidence/windows-native-tabs-fonts-20260921/README.md)\nand updated acceptance roadmap are preserved by an additive merge into this\ncheckpoint. OPF/coordinated premerge runs **35647284713/35647284684** and\npostmerge runs **35648525334/35648525363** all passed on their first attempts.\nThe [compact receipt](evidence/inspector-current-actions-20260921/core106/acceptance-receipt.json)\nseparates exact-head CI, byte verification and portable validation from the\nWindows supervisor's native results. The bounded Carlito control passes; general\nphysical glyph identity, fallback/synthesis, embedding and mixed-size fidelity remain open.\nThe [PPTX48/49 status receipt](evidence/inspector-current-actions-20260921/core106/pptx48-49-ownership-checkpoint.json)\nrecords both harness PRs merged, remote main `9e8a381` and first-attempt pre/post CI\nsuccess. It does not extend native behavior beyond the recorded controls.\n\n[Core107](https://github.com/OpenPresentation/opf/pull/107) is accepted at\n`5bc0d3f89414b382b2ce48452c7e56e5d66aaf74`, with reviewed and accepted tree\n`ffdc678beadf0808bc717d67e7fc0a9ec4790127` and all 87 reviewed delta blobs equal.\nBoth original postmerge push runs **35652360502 / 35652360551** passed on attempt 1\nunder Node24.20.0. The [compact audit](evidence/font-readiness-acceptance-20260921/README.md#core107-acceptance)\nkeeps the earlier automatic premerge cancellations **35650767479 / 35650767380**\nseparate from later automatic successes **35650769661 / 35650769719**; explicit\nGitHub annotations establish concurrency supersession, without naming an actor.\nNo rerun or descendant success rewrites those outcomes.\n\n[Core108](https://github.com/OpenPresentation/opf/pull/108) merged at\n`9b277e140863389ae06c681a529fac32df8c912c`; [core109](https://github.com/OpenPresentation/opf/pull/109)\nmerged at `b2711549eba48a52f036afd02ee52b761fa0a5f9`. Captured PR-head OPF and\ncoordinated checks passed. Their [coordination receipts](evidence/font-readiness-acceptance-20260921/README.md#native108109-coordination)\nrecord clean primary fast-forward and additive preservation of native105/106/108/109\nevidence, roadmap and native109's matrix addition. These PR receipts do not assert\nunrecorded postmerge CI or complete native compatibility.\n\n[Core111](https://github.com/OpenPresentation/opf/pull/111) merged as\n`3c5048522714365a41d9b5b9ba81620affae718b`, exact reviewed head `ef43a1bd`, with\nOPF/coordinated PR checks passing. Its [accepted Windows wrap-up](handoff-windows-native-2026-09-21-wrap-up.md)\nand font-inventory evidence are preserved byte-exact. [Renderer30](https://github.com/OpenPresentation/opf-render/pull/30)\nmerged as `c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b`, exact reviewed head `a73d739`,\nwith CI 35661051100 passing. The [final supervisor notice](evidence/font-readiness-acceptance-20260921/week-wrap/native-supervisor-final-notice.json)\nreports renderer postmerge 35661504472 passed and core111 postmerge 35662159658/35662159864\nstarted; the latter are not claimed green. It reports no owned Office/helper/font\nregistration left outstanding. This dated observation does not establish future\nhost readiness. All 12 native-batch PRs are merged; compatibility and publication\nremain separate. No Office call or native rerun was made by this checkpoint.\n\nColorRef slot/role/variable resolution, variables, payload ids/extensions and\ncatalog bundling are published. Bundling inlines catalog records, not remote\nmedia/data. Furniture (`furniture-flow-v2`) exports as editable tagged slide\nshapes with `OPF_FURNITURE_V1` provenance. Controlled imports use current native\ncontent; arbitrary Office round-trip and real native Header/Footer (`p:hf`)\nare not certified. Content, source whitespace, metadata, reading order,\nformatting, human adjustments and undo remain required preservation boundaries.\n\n## Public workflow acceptance\n\n[Site44](https://github.com/Data-Advantage/openpresentation-site/pull/44) passed\nfresh CI and canonical acceptance at the exact READY deployment for `a85bcc77`.\nIts documentation source `120a770` has the same tree as accepted core PR98.\nThe live verifier passed **321 checks, 11 pages and 18 raw resources**; two\nadditional browser flows passed agent installation/navigation and actual\nJSON/SVG/PPTX downloads. Current pins, full guide/skill content, raw-manifest\nhashes and installed runtime/catalog/example integrity agree with the reviewed\nbuild. Three live screenshots were reviewed. Binary evidence remains available\nas raw resources and linked reports without being decoded into guide prose.\n[Gallery36](https://github.com/Data-Advantage/pptx-gallery/pull/36) is also merged,\npost-merge green and verified from canonical `/docs` to `/editor`.\n\n[App45](https://github.com/Data-Advantage/pptx-dev/pull/45) is merged and deployed\nat `e37e0da`, with green Linux/Windows PR and post-merge CI. The full canonical\nrun passed **14/18** workflows. Two failures exposed implicit paste formatting;\none exposed a preview helper's shorter budget before the existing font-readiness\nallowance; one was an unexplained intermittent CRLF Author suggestion popup.\nGreen CI therefore does not establish complete production acceptance.\n\n[App46](https://github.com/Data-Advantage/pptx-dev/pull/46) is merged as\n`c558dcc3a2e362bc238eb433a2db97fa682dfcc8`, with an identical reviewed tree and\ngreen Linux/Windows pre/post-merge CI. It disables implicit paste formatting while retaining\nexplicit Format Document and exact undo, and carries the original long-quote\nfont-readiness budget through the preview helper. Fresh local checks pass 622\nunit tests and 19 browser workflows. The old-build control rewrites 302 source\nbytes into 443; the fixed build retains all 302.\n\nThe exact READY canonical deployment passed **11/19** in one fresh run. Wide and\nreordered Inspector source preservation, CRLF categorical apply/undo/redo, and\nwide/portrait long-quote export pass. Eight checks stop at diagnostic, worker,\npopup or canvas readiness. No source mismatch was observed, but the focused\nwarm-paste regression and narrow workflow did not reach their acceptance checks.\nThe original 14/18 run and unexplained missing-popup failure remain preserved.\nDo not replace either failed production result with green local/platform CI.\n\nA separate public-export/Monaco review proved that the pre-app47 adapter rejected\n28 of 32 layout choices in the captured fixture. [App47](https://github.com/Data-Advantage/pptx-dev/pull/47)\naddresses that defect at reviewed head `a5201c88a219262b21a6c7fd4ef49a01a5bbe0a3`:\nsingle-line scalar completions carry disjoint structural edits, retain unchanged\nsource tokens, and invalidate stale model/source/catalog results. Public\n`replaceFieldOption` supplies layout semantics. Unchanged current values are\nomitted from actionable OPF suggestions: the rejected empty-edit prototype consumed\nan undo step. The final source passes **627 unit tests and 24 browser workflows**,\nzero browser retries, with reviewed popup screenshots and exact compact LF/CRLF\nsource/undo checks. Installed Monaco validates **1,829 alternatives across 59\ncursor positions**. The [committed app evidence](https://github.com/Data-Advantage/pptx-dev/tree/a5201c88a219262b21a6c7fd4ef49a01a5bbe0a3/docs/evidence/completion-ranges-20260921)\nretains controls, tests, hashes and rejected-prototype evidence. At this checkpoint,\n[CI run 35620611535](https://github.com/Data-Advantage/pptx-dev/actions/runs/35620611535),\nincluding Linux and Windows, is green. App47 merged as\n`0f35352a1445f56ad4bb7c9f4c5609e01f2dd9ae` with the exact reviewed tree\n`203bdab509d05911f04f234d996f9c91f2b5e4f2`. The historical App47 production\ndeployment was [READY](evidence/completion-acceptance-20260921/app47-deployment.json)\nwith canonical aliases. [Post-merge CI 35621722214](https://github.com/Data-Advantage/pptx-dev/actions/runs/35621722214)\nis also green on Linux and Windows. The one fresh [canonical run](evidence/completion-acceptance-20260921/canonical/REPORT.md)\npasses **23/24**, including all five\nnew completion cases. The existing LF Author categorical third-popup assertion\nstill fails at the unchanged five-second deadline; the CRLF counterpart passes.\nThe [post-run receipt](evidence/completion-acceptance-20260921/app47-deployment-post-run.json)\nconfirms the same READY deployment and accepted SHA. This is failed overall\ncanonical acceptance, not a complete pass. It does not erase either prior failed\nrun, resolve their readiness causes or explain the intermittent popup. At that pre-App53 checkpoint, broader byte preservation for Author canvas serialization and JSON-file downloads remained open; App53 corrects those bounded paths below.\nThe app47 failure reached the third request after Dark and document-local\nchoices passed exact apply/undo/redo. Go to Line and the 11:18 cursor assertion\npassed. All 115 recorded requests had finished before Control+Space, with no\nHTTP/network failures or recorded page error. Monaco appeared focused in the\nsaved action snapshot, but keybinding delivery and provider invocation were not\nrecorded. A pending network request therefore does not explain this failure;\nno network or focus cause is established. Keep it distinct from app46's delivery delays.\nThe exact-head preview is READY but protected and has not been browser accepted.\nIndependent local review is recorded; Bugbot's neutral usage-limit result did\nnot review the PR. Ordinary Monaco JSON schema suggestions remain distinct from\nthe OPF provider's actionable alternatives.\n\nA [source-only audit](evidence/completion-acceptance-20260921/source-preservation-audit/REPORT.md)\npins the pre-App53 serialization paths. Author canvas edits/undo and preview Copy/JSON export serialized the document; Inspector JSON download also serialized. Untouched 325-byte LF and 332-byte CRLF fixtures both became 473 bytes. Semantic\ncontent survives, but raw spacing, escapes, line endings and source offsets do\nnot. **Author code-tab same-format JSON export already retains raw source**;\ndo not describe every Author download as broken. Existing Inspector source-bridge\nedit/undo/redo retains exact bytes in the same reproduction. This is historical source and serializer evidence for App47, before the accepted App53 correction described below. Exact imported JSON-file bytes remain a separate\nboundary because the current import path discards them after parsing.\n\n[App53](https://github.com/Data-Advantage/pptx-dev/pull/53) merged as\n`e40c287b64fcbcfb85fb4a8a50641aea8e3e54a8`. Its accepted tree\n`64a0dbd702f43c87206c8a9fe16d06fe3ac68800` exactly matches reviewed head\n`b33dc18e8c35803386feb80e7822f240a9671243`; the primary checkout is clean.\nThe [canonical production run](evidence/author-source-acceptance-20260921/canonical/REPORT.md) passes **29/29**, zero retries, with the same exact READY e40c287 deployment before and after execution. Author reuses the source bridge for canvas edits/history and guarded\npost-commit reads; same-format preview Copy/JSON export and Inspector JSON download\nretain the raw buffer. The session hook ignores object-key order while preserving\narray order and values, avoiding a redundant reimport-history entry. Explicit\nformat conversion and original imported-file spelling remain separate boundaries.\n\nThe merged change also guards exact Ctrl+Space through the supported public Suggest\naction when the originating editor has text focus. Local diagnostics observed\nstale Quick Input context at the same time as editor focus, preventing OPF\nprovider invocation. The installed higher-priority binding explains the competing\nroute; its chosen internal command is inferred from source and public conditions.\nThe guard leaves actual Quick Input focus, visible suggestion details, modifiers,\ncomposition and unsupported actions on their existing routes. No retry, private\ncontext mutation or timeout increase was introduced.\n\nFresh local Node24 checks pass **627 unit tests and 29/29 browser cases in\n88.78 seconds**, zero retries, including all original tests and two keyboard\nboundary cases. [Committed evidence](https://github.com/Data-Advantage/pptx-dev/tree/b33dc18e8c35803386feb80e7822f240a9671243/docs/evidence/completion-keyboard-20260921)\nretains exact downloads, source/build identities and reviewed screenshots. The\nWindows-only timing helper was inactive during the macOS suite; its subsequent\nfile-retention correction passed type/lint and Node controls, with product and\nactive macOS assertions unchanged. Fresh application\n[CI35631886222](https://github.com/Data-Advantage/pptx-dev/actions/runs/35631886222)\npassed Linux and Windows on its first attempt, each with 627 unit tests and\n29 browser cases. Separate artifact CI also passed both platforms on attempt 1.\nPostmerge [application CI35633321018](https://github.com/Data-Advantage/pptx-dev/actions/runs/35633321018) fails Linux **28/29**, while Windows passes **29/29**; both pass 627 unit tests. Separate artifact CI passes both platforms. The [failure receipt](evidence/author-source-acceptance-20260921/app53/postmerge-ci/README.md) records five default-deck canvases after the shared-load toast, before security assertions. The current canonical pass does not erase this failed gate. The retained b33dc18\n[preview](evidence/author-source-acceptance-20260921/app53/keyboard-preview.json)\nis READY with target null and has no browser acceptance. Phase4's ten healthy diagnostic cases\nrecord 30 keys and provider calls but no stale-context overlap, so causal stress\nremains inconclusive. The existing suggestion-details pane is clipped; popup\nvisibility does not accept that visual limitation.\n\nThe initial **24/27** reimport/undo regression, subsequent **26/27** local popup\nfailure and old-head Windows **26/27** shared-load failure remain preserved. The\nWindows security assertions were not reached; its post-failure snapshot loaded\nthe correct deck, and no concrete import race was established. Passive sanitized\ntiming files retain the original test actions and budgets. The fresh successful\nWindows run uploaded its artifact, but only Author timings survived; the Inspector\npagehide snapshot is missing. This does not explain the old readiness delay. The\n[follow-up ledger](evidence/author-source-acceptance-20260921/README.md) keeps these\nruns separate from the current local/canonical passes and failed postmerge Linux gate.\nThe [App53 issue88 receipt](evidence/author-source-acceptance-20260921/issue88-app53-diagnosis-updated.json) remains OPEN. The canonical evidence retains 31 saved downloads, 28 literal-byte JSON checks, source/deployment bindings and five reviewed screenshots. Remaining readiness, preset/source preservation and compatibility work still require correction and fresh acceptance.\n\nThe [postmerge trace diagnosis](evidence/author-source-acceptance-20260921/inspector-share-diagnosis/REPORT.md)\nproves wrong-document automatic share-hash publication during import. [App54](https://github.com/Data-Advantage/pptx-dev/pull/54)\nwas unmerged at that checkpoint. Its original `60c91f6b97c75636f533202f4112e09dc01144e4`\ncorrection validates authoritative source/format before and after encoding,\nretains navigation/load guards and removes obsolete `import=hash:` so refresh\nuses the current fragment. URL handoffs preserve semantics, not raw spelling.\n\nThat original source passed 659 unit tests and 31 local browser cases. First-attempt\napplication **35638158483 failed**: Linux **31/31**, Windows **30/31**, with 659\nunit tests passing on each. Both new publication cases passed. The unchanged\n45-second security deadline expired during Author Preview readiness after\n31.400 seconds of navigation; a 1,490-byte decoded local script took 30.026 seconds.\nThe [frozen diagnosis](evidence/inspector-share-acceptance-20260921/app54/windows-timeout/REPORT.md)\nrecords bounded observations and an unknown cause. Inspector security checks\npassed; late Author assertions do not count as an in-budget pass. Browser History\ntransition tests permit the preceding accepted document before first new publication;\nheld-promise controls establish the narrower pending-encode guard. This limitation remains.\n\nThe product correction was added at `57e5e59fddbc94346f142dc12d86a916228bf2ae`, tree\n`d9c3aab543c6a886a85c6ec07dd55e5ab22590cd`. It corrects explicit Inspector actions.\nCopy, JSON, Share, PPTX/PDF, Open in Author and Deckchat commit pending public\ncanvas drafts, then use one guarded current-source snapshot for payload/filename.\nA rejection latch survives pointer-blur recovery and reports the original error\nbefore permitting retry. New source/format, external load/navigation, unmounts\nand newer actions suppress late output; owned automatic hash changes alone do\nnot cancel it. Raw malformed Copy and accepted JSON bytes remain available.\nAn already started clipboard/download side effect cannot be retroactively canceled.\nPDF/Deckchat checks use local mocks; remote services are not accepted by them.\n\nPortable startup now prepares the generated standalone server with runtime assets,\nfonts and traced schema inputs, and verifies required inventory/build identity.\nThis is not proof of the original Windows stall's cause or resolution. Published\npackage pins and the lockfile remain unchanged.\n\nThe combined runtime source passes **692 unit tests**, **7 standalone Node controls**,\nfocused **8/8** and full **39/39** local browser cases in **113.565407 seconds**,\nzero retries, with reviewed screenshots. [Immutable app evidence](https://github.com/Data-Advantage/pptx-dev/tree/57e5e59fddbc94346f142dc12d86a916228bf2ae/docs/evidence/inspector-action-snapshots-20260921)\ncontains the reviewed 93-file bundle and both original failed traces. The old-head\nnegative control copies prior source while an inline draft is active. The initial candidate focused **7/8**\nrun did not establish accepted pasted source before releasing mocked PDF 401;\nthe final test adds public visible-code assertions with unchanged budgets and\nproduct bytes. CRLF clipboard ingress normalized 279 bytes to the existing LF\nmodel's 274 bytes; action preservation applies to the accepted raw buffer, not\nthat ingress boundary. These are explicit acceptance limits.\n\n**The original 57e packaging gate failed.** After the evidence bundle was added,\nthe `57e5e59` Vercel preview failed with `module_not_found`; exact preview build\nlogs were unavailable. Fresh CI **35645493900** fails Linux and Windows typecheck\nafter passing 692 units and 7 standalone controls per platform. Its two TS2307 errors are in archived\n`.spec.ts` evidence copies. The local runtime build predates that evidence addition.\n[The first-attempt receipt](evidence/inspector-current-actions-20260921/app54/first57-ci/REPORT.md)\nrecords skipped CI build/browser steps and no uploaded artifacts. A byte-preserving\n`.ts.txt` archive correction produced captured App54 head\n`6dfc2584698c1306505929a2bc3e427996d66563`, tree\n`f3279495f7685945b7541c90d24f7239407639a0`. Both archived source snapshots retain\nexact bytes, all 305 product/test inputs are unchanged, and final-tree local\ntypecheck passes. The [corrected receipt](evidence/inspector-current-actions-20260921/app54/corrected6df/commit-receipt.json)\nbinds the 106-file evidence bundle, including original failed-CI receipts.\n\n[Exact-head CI 35646213757](evidence/inspector-current-actions-20260921/app54/corrected6df/ci/REPORT.md),\nattempt 1, passed frozen install, audit, 692 unit tests, seven standalone controls,\napp/SDK typechecks and build on both platforms under Node 24.20.0. Linux job\n**106487202579 passed 39/39** browser cases. Windows job **106487202266 failed\nbefore any browser test started**: the standalone launcher reported `EPERM` while\nstatting `.next/standalone/node_modules/.pnpm/next@16.3.5_@babel+core@7.2_620aa7aa2232a0f82d57635d189e7ba9/node_modules/react`,\nand Playwright's webServer exited 1. No test-result or timing artifact was uploaded.\nThe exact dependency-link filesystem cause is not established by the receipt;\nthis failure is distinct from the original 60c Windows resource-delay timeout.\nNo CI or browser rerun replaces any first outcome.\n\n[Preview metadata](evidence/inspector-current-actions-20260921/app54/corrected6df/ci/preview-initial.json)\nbinds READY deployment `dpl_CDcgpgLm4UYSahkzFAiY9qwocRCR` to exact 6dfc258.\nIt has no preview-browser or production acceptance. The captured-head check runs\ncontain only the two application jobs and submitted PR reviews are empty; no\nautomated review for that head is claimed. Python artifact-compatibility CI did not\nmatch this patch's changed paths and was not triggered, not a fresh pass.\nThe [current checkpoint](evidence/inspector-current-actions-20260921/README.md)\nretains the immutable source, CI and preview receipts. At that 6df checkpoint App54 was unmerged\nand undeployed to production. App53 `e40c287` was canonical production at **29/29**,\nwith its failed Linux postmerge gate retained. Those historical results remain\nseparate from the current App54 release checks below. Issue88 remains open.\n\nThe [startup-link correction at 4338e57](https://github.com/Data-Advantage/pptx-dev/blob/4338e57b951c469d5c5f239b78a303fbad3c745e/docs/evidence/standalone-windows-links-20260921/README.md)\nthen passed Linux **39/39** but failed Windows **38/39** in first-attempt\napplication **35649707689**; both passed 692 units and 13 standalone controls.\nWindows executed the browser suite, but its initial retained-slide canvas target\nwas absent at the unchanged five-second assertion, before edits or recovery.\nCorrect incoming source was present with **Loading slide fonts\u2026**. The manifest\nstarted about 3.175s into the assertion; 22 of 33 TTF requests completed with HTTP200\nand eleven had no completed response by teardown. This records readiness failure,\nnot a source overwrite, permanent network stall or proven converter-only cause.\nThe [preserved failure and diagnosis](evidence/font-readiness-acceptance-20260921/README.md#preserved-failed-windows-gate)\nremain distinct from the earlier 60c, 57e and 6df outcomes.\n\nApp54 font-preparation revision `a4eb88ab7aa585c9efb91de4c190c1f1c0c7d0eb` has tree\n`031d893855a540fd2a4d2ec2162605817b8dde16`. Full font acquisition overlaps\nconverter warmup while canvas/shared-lease readiness still requires **both**;\nconverter failure disposes any separately successful font preparation. This retains\nthe cold/offline export barrier, all **33 faces / 9,317,044 bytes**, font manifest,\nsubstitution policy, measurement and `document.fonts.ready` gates. The font set\nand browser assertions/budgets are unchanged; no new fallback was introduced.\nFresh macOS Node24.21.0 acceptance passes **704 units, 13 standalone controls and\n39/39 browsers**, zero retries, in **111.898960 seconds**, including offline\nAuthor/Inspector export and reimport. Source and reviewed screenshots are bound in\n[immutable app evidence](https://github.com/Data-Advantage/pptx-dev/tree/a4eb88ab7aa585c9efb91de4c190c1f1c0c7d0eb/docs/evidence/font-preparation-concurrency-20260921).\n\n**The original a4eb gate failed.** First-attempt application **35654753237** passes\nLinux **39/39** and fails Windows **38/39**; both pass 704 units, 13 standalone\ncontrols, typecheck and build. The Windows Open in Author case fails its existing\nfive-second URL assertion at `inspector-actions.spec.ts:176`, reporting an empty\nmatcher received value while navigation was pending, not an observed empty actual\nURL; later source assertions are not reached. The original 4338 initial\nfont-readiness case passes in this run. This is a separate failed gate, with no\nestablished cause or data-loss finding. The [frozen final audit](evidence/font-readiness-acceptance-20260921/app54/final-a4eb-ci/release-audit.json)\nbinds exact source, jobs and artifacts; the original failed trace is retained\nprivately with hash/storage receipts and its historical error context. The [separate diagnosis](evidence/font-readiness-acceptance-20260921/README.md#author-navigation-diagnosis-and-prospective-policy)\nrecords an HTTP200 Author document and delayed script response. The latter's ETag\nimplies 1490 bytes, with 766 compressed bytes recorded; its actual body is absent.\nThe reviewed final frame shows Loading Author; later Author DOM is not acceptance.\nNo server/runner/network root cause or acceptable latency is established.\nThe Python artifact workflow is not applicable under its full-PR path filters,\nnot a fresh pass. Exact a4eb preview metadata is READY, but the\nunauthenticated Inspector request redirects to Vercel sign-in; no preview-browser\nacceptance occurred. At that a4eb capture App54 was unmerged and App53\n`e40c287` was live. The [current checkpoint](evidence/font-readiness-acceptance-20260921/README.md#held-release-checks)\nretains failed platform evidence separately from future release checks. The suggestion-details\nUI candidate remains separate and unreleased; it supplies no acceptance here.\n\nApp54 merged as `8f54228a9e38a1dcc0bf8188bcdd519795b3799a` from reviewed head\n`79ba0157984fce8405eeb786b8ede1a4e59ba138`, retaining identical tree\n`3639d3c14daec94d13711fa2b10f2b927df45eca`. Its test-only policy registers\n`waitForURL(..., { waitUntil: \"load\" })` before the real action, matching `page.goto`\nwithin the unchanged 45-second test and default 5-second content budgets. All original\noracles remain. It deliberately removes the incidental five-second navigation deadline;\nit is not a product/routing fix or retrospective acceptance of a4eb.\nFresh local acceptance passes **39/39**, zero retries, in **112.780048 seconds**,\nwith frozen install, build and prepared-tree typecheck. The initial missing-generated-\nschema typecheck failure remains preserved. Root binds 702 inputs/33 symlink targets\nand reviewed handoff images in the [immutable 202-file app evidence](https://github.com/Data-Advantage/pptx-dev/tree/79ba0157984fce8405eeb786b8ede1a4e59ba138/docs/evidence/author-navigation-policy-20260921).\nUnits/standalone controls were not repeated locally for this test-only change.\nFirst-attempt application **35659187971 passes**. Each Linux/Windows job passes\n**704 units, 13 standalone controls and 39/39 browsers**. Exact 79ba READY preview remained\nprotected by sign-in, with no preview-browser acceptance. The [merge binding](evidence/font-readiness-acceptance-20260921/app54/merged8f/merged-tree-binding.json)\nverifies identical tree and 203 delta blobs. Production deployment\n`dpl_H1FtXx1QSuGxn3MwzJwWJpGtRg8b` is READY at exact 8f, but its single canonical\nrun **passes 34/39 and fails five strict no-write assertions**, zero retries in\n148.112604s. The [safe request audit](evidence/font-readiness-acceptance-20260921/app54/canonical8f/write-audit/REPORT.md.txt)\nfinds ten Clerk environment POSTs: nine HTTP200 requests record zero-length bodies;\none is incomplete. No fixture-content needle matched captured fields or decoded\nJWT payloads, which does not prove anything about uncaptured data. The three action\ncases passed prior source/payload/clipboard checks but did not reach their final\npage-error assertions; both share/refresh cases passed their page-error checks.\nOf 29 scoped auth/config files, 28 are identical to App53; only package scripts changed.\nThe [recorded disposition](evidence/font-readiness-acceptance-20260921/app54/canonical8f/disposition.json)\nkeeps the deployment live because this evidence shows no introduced authored-content\nupload/source regression; this is not a canonical pass or permission to weaken the\nno-write policy. No rollback, test change or retry is performed. Raw authentication-bearing\ndiagnostics remain durably private; safe reports and original hashes are retained.\n[Postmerge CI 35660464578](https://github.com/Data-Advantage/pptx-dev/actions/runs/35660464578)\nalso fails on attempt 1: Linux 39/39 passes; Windows 38/39 fails at initial gallery\nrail title visibility (`inspector.spec.ts:125`, unchanged 5,000ms). Correct source,\nclean schema and one slide were present with Loading slide fonts; later preview,\ndownload, undo/redo and shared-reimport assertions were not reached. Both platforms\npass 704 units, 13 controls/typecheck/build. The revised Open in Author case passes\nWindows, but does not clear this separate failure. The [final postmerge audit](evidence/font-readiness-acceptance-20260921/app54/merged8f/postmerge-ci/REPORT.md.txt)\nretains original outcomes and artifact hashes/CRC; unaudited raw traces remain\nprivate. No cause, acceptable latency or data-loss finding is established. The [current receipts](evidence/font-readiness-acceptance-20260921/README.md#current-app54-navigation-policy-head)\nkeep merged/live source distinct from incomplete canonical acceptance and original failures.\n\nThe [remaining-writers source audit](evidence/author-source-acceptance-20260921/remaining-writers/REPORT.md)\nidentifies preset Undo all as the next priority. Its proposed guarded correction\nremains unapplied pending the separately requested approval. Separate [local negative controls](evidence/author-source-acceptance-20260921/preset-negative-controls/REPORT.md)\nconfirm that it discards both New run and imported replacement presentations. The\nordinary-edit scenario stopped at its raw-buffer LF/CRLF precondition and did not\nattempt Undo all. These are unresolved local findings, not production proof.\nLocal author, filename, gallery and preset writers still serialize source; the\nschema writer is dormant. Those paths, account/agent/metadata serialization and\nimported-file spelling remain outside App53's bounded correction.\n\nOne [readiness diagnostic](evidence/completion-acceptance-20260921/readiness/REPORT.md)\nfound healthy computation after worker initialization and did not reproduce the\nproduction delay. Original traces show slow/pending worker bootstrap assets,\ninitial page delivery and a separate preview/font dependency path. The **11/19\nresult remains failed**. A [static worker preparation audit](evidence/completion-acceptance-20260921/worker-candidate/WORKER-AUDIT.md)\nis a candidate only: no worker preparation, worker routing, timeout, font or\ndeployment changes implement that plan. Its dormant external-diff dependency and\noffline/deployment requirements still need review and acceptance if pursued.\n\nKeep [issue88](https://github.com/OpenPresentation/opf/issues/88) open until the\nreadiness, completion and source-preservation work passes fresh canonical\nchecks, with remaining limitations resolved or explicitly scoped.\nApp46's negated closing-keyword prose accidentally auto-closed issue88 at\n14:50:21 UTC; it was reopened at **15:18:18 UTC** and the PR body corrected.\nThe [issue receipts](evidence/completion-acceptance-20260921/issue88/issue88-reopened.json)\nrecord that administrative correction, not completed acceptance.\nThe [durable ledger](evidence/issue88-final-20260921/README.md) retains successful\nchecks, failed runs and their diagnoses; a later green run does not erase an\nunexplained failure.\n\n## Next-week queue\n\nWork is paused for the week, not complete. The queue below is a durable handoff;\nno implementation or new investigation begins until the user resumes the work.\n\nThe [seven-repository PR snapshot](evidence/font-readiness-acceptance-20260921/week-wrap/open-prs.json)\nis a read-only queue captured before final wrap-up PRs. Dependency-bot proposals,\nincluding Node26 types and TypeScript major upgrades, are unreviewed and outside\nthis wrap-up: do not merge them or update pins. The five geometry and two native-HF\ndrafts are unchanged; stale changelog drafts remain closed. The [Windows wrap-up message](https://github.com/OpenPresentation/opf/pull/110#issuecomment-5768032573)\nwas delivered through PR110; direct task routing remains unavailable.\n\n- App54 is merged/live at 8f; retain the failed canonical gate (34/39 passed) and separate\n postmerge Windows initial-font-readiness failure. Both bounded audits are complete\n and the deployment\n is kept. After explicit resumption, review the strict network-write contract and\n unreached page-error oracles before choosing a correction or new acceptance run.\n No rerun, test relaxation or new investigation is started during this wrap-up.\n Do not relabel the original a4eb or 4338 failures.\n- [Suggestion-details PR55](https://github.com/Data-Advantage/pptx-dev/pull/55) is draft\n at 9b441aafe66a8849d3e212df04348e762d9866da on accepted 8f. Its 43 local cases pass;\n [Current check metadata](evidence/font-readiness-acceptance-20260921/week-wrap/ui55-wrap-current.json)\n records Linux/Windows run 35660801988 and Vercel SUCCESS; no detailed CI audit was\n performed here. The original draft receipt remains preserved. Review it independently\n next week; it is neither merged nor production accepted.\n- Keep the [parked preset Undo all proposal](evidence/font-readiness-acceptance-20260921/preset-proposal/README.md)\n approval-held and unapplied. Its source-preserving proposal\n must not overwrite intervening human edits, New run or imports; implementation and\n fresh undo/redo controls follow the separately requested approval and rebase to\n current accepted app source. The archived patch/companions are inert data; earlier\n bridge-unit/static checks do not establish integrated runtime acceptance.\n- Accepted core111 and renderer30 source/evidence are recorded above and in the\n [Windows wrap-up](handoff-windows-native-2026-09-21-wrap-up.md). Their registry\n package versions remain unchanged. On resumption, refresh exact release status\n before extending source-linked results to a new packed or published consumer.\n- The Windows supervisor alone owns Office/font registration. Native allowlist\n failure, tab 0.02pt, physical glyph identity, mixed-size persistence and embedding\n remain open. Inspect the host afresh before any future bounded native operation;\n the dated cleanup observation is not continuing authorization or readiness proof.\n Browser readiness passes do not close native/font gates.\n- Keep issue88 and the overall goal open. Required deterministic repair and broader\n source/metadata preservation remain incomplete. Geometry stays deferred as five\n coordinated drafts; renderer issue24 stays0.1px, native `p:hf` stays roadmap, and\n selectable PDF/SVG/Mermaid follow font reliability. No new work starts as part of\n this week's wrap-up.\n\n## Compatibility and remaining goal\n\n- Renderer issue24 remains deferred at the unchanged **0.1px** tolerance.\n Preserve rejected experiments and archived shaping branches. No platform\n offsets, weakened assertions or golden changes to hide residuals.\n- Native Office issue87 remains separate. The [accepted native B/C evidence](evidence/windows-native-edits-20260921/README.md)\n covers finite picture/furniture edits, current-content provenance reimport and safe fallback,\n plus production notes packaging and two controlled reordered-file refusals.\n UI Change Picture changes geometry; longer native header text clips and duplicated\n tagged headers overlap. Failed refusal-worker cleanup remains distinct from later\n empty-workspace observations. This is not general layout, reflow or Office fidelity.\n Accepted [core106 tab/Carlito evidence](evidence/windows-native-tabs-fonts-20260921/README.md)\n retains native tab target error **0.022655487060546875pt** and tab/literal difference\n **0.022678375244140625pt**, above the unchanged **0.02pt** gate. Its bounded\n four-face Carlito edit control passes exact text/style persistence, zero observed\n bounds drift, matching rasters and owned font cleanup. General physical glyph-font\n identity, fallback/synthesis, embedding and mixed-size table fidelity remain open;\n embedding was disabled.\n [Core108's offline nine-pair analysis](evidence/windows-native-tab-analysis-20260921/REPORT.md)\n finds finer saved tab coordinates than observed tabbed-character starts, which\n match a 0.05pt-compatible pattern in this finite sample. Relative versus absolute\n placement remains indistinguishable; no internal engine cause or compensation\n is established, and the 0.02pt tab gate still fails.\n The offline tab verifier fails on local Python3.9.6's unsupported\n `Path.write_text(newline=...)`; explicit Python3.12.14 replay passes all nine\n pairs/eight checks byte-identically. This portability caveat does not change\n accepted evidence or native measurements; the mixed-table verifier passes on3.9.6.\n [Core109's read-only mixed-table observation](evidence/windows-native-mixed-table-20260921/README.md)\n retains 245 characters, a literal tab and five rich runs with outer geometry within\n 0.02pt and owned cleanup. Native soft-line boundaries 92/194 differ from estimated\n preview 78/172; default native tab spacing is 72pt. This does not pass table\n edit/save/reopen, browser/native raster agreement or physical glyph identity.\n The Windows supervisor\n retains sole Office control. This documentation task makes no Office/COM calls;\n restricted Aptos4.40 remains excluded without permission.\n The Windows supervisor's [core110](https://github.com/OpenPresentation/opf/pull/110)\n is merged as `4f7a4bd494f1a873319eff897423d301d1cfc9d6`, from exact reviewed\n fc3c36e with all four required PR checks passing. It preserves rich-tab source\n offsets/styles using the existing four-space advance convention. The renderer\n companion is merged as renderer30 `c8d7d5c`; the [accepted Windows wrap-up](handoff-windows-native-2026-09-21-wrap-up.md)\n records its finite source-linked checks, preserved original CI failure and unchanged\n registry consumer. Package versions, lockfiles, release/site pins, schema, goldens\n and tolerances remain unchanged. Coordination was [delivered on PR110](https://github.com/OpenPresentation/opf/pull/110#issuecomment-5767980133);\n direct task routing remains unavailable. Windows retains sole Office ownership.\n Accepted core111 preserves the read-only Carlito+Aptos allowlist **FAIL**, with\n no embedding and an offline correction to the original PowerShell5 cleanup-report\n classification. The raw failure and owned-close/four-removal evidence remain\n intact. Collection names do not establish physical glyph identity or compatibility.\n- Keep the five geometry drafts deferred as one coordinated set: core94, renderer27,\n editor25, PPTX42 and site40. None is accepted; do not merge site40 independently.\n Native HF documentation drafts core92/PPTX41 remain roadmap; do not implement\n p:hf here.\n- Superseded changelog drafts editor24/render26/PPTX40 are closed with branches\n retained. Historical furniture geometry and provenance facts remain in the\n cleanup evidence; current release headings name the newer train.\n- Fresh source/installed-package/browser/CI checks are required for changed code.\n A green portable gate is not native or font acceptance. Public applications\n require their own PR, CI and exact canonical-browser receipts.\n- Later, not now: move `docs/fixtures/color-references.opf.json` into examples\n with a reviewed renderer golden/corpus increase from 126 to 127+, optional\n PPTX schemeClr/theme writing, and editor canvas named-color fidelity.\n- Broader deterministic repair, full visual editor, fonts/IME/bidi and source\n preservation coverage remain on the [developer roadmap](plans/developer-adoption-20260915.md).\n PDF is raster-backed; selectable PDF and SVG/Mermaid follow font reliability.\n\nKeep the workflow offline and provider-neutral without a required account or\nmodel call. Preserve exact evidence, authored content and explicit adjustments.\nDo not mark the overall goal complete while required work remains unresolved.\n"
110
+ },
111
+ {
112
+ "slug": "handoff-2026-09-29",
113
+ "file": "docs/handoff-2026-09-29.md",
114
+ "title": "September 29 review checkpoint",
115
+ "markdown": "# September 29 review checkpoint\n\nThis is the frozen 08:25 UTC checkpoint. The [later runtime checkpoint](handoff-runtime-2026-09-29.md) records subsequent merges, original CI outcomes, and current release holds; the earlier states below are retained as history.\n\nThe overall delivery goal remains open. This checkpoint separates published\npackages, merged source changes, reviewed repairs, and native compatibility.\nPR states below were captured through 08:25 UTC. Use Node 24 for new verification. The [compatibility matrix](compatibility-matrix.md)\ndescribes the published subset; the [font fidelity program](programs/font-fidelity-everywhere/README.md)\nand its [burndown](programs/font-fidelity-everywhere/burndown.md) track the wider\nunreleased work. No package publication or website deployment was performed by\nthis review task.\n\nThe [compact evidence receipts](evidence/pr-review-20260929/README.md) retain\naccepted source identities and original CI outcomes, including skipped steps.\n\nFresh [npm metadata](evidence/pr-review-20260929/registry-metadata.json) still\nreports core **0.11.0**, CLI/render **0.9.0**, PPTX **0.9.1**, and editor\n**0.8.0**, all declaring Node 24.x. This read-only metadata check does not\nreplace fresh installation and acceptance of a future release candidate.\n\n## Completed cleanup\n\n| Change | Accepted commit / result | Verification |\n| --- | --- | --- |\n| [Core #143](https://github.com/OpenPresentation/opf/pull/143): Node 24 development types patch | `96fbd69ac1aee1fd3dfbc9f3e27013e11c0921f6` | Original postmerge [OPF](https://github.com/OpenPresentation/opf/actions/runs/36537723977), [coordinated packages](https://github.com/OpenPresentation/opf/actions/runs/36537724102), and [Windows/macOS CLI](https://github.com/OpenPresentation/opf/actions/runs/36537723829) runs passed. |\n| [Editor #33](https://github.com/OpenPresentation/opf-editor/pull/33): CodeMirror/Lezer patches | `d0c95a16b50eccb3eee695ace44cc6c3a6754f2f` | Original postmerge [editor CI](https://github.com/OpenPresentation/opf-editor/actions/runs/36537730926) passed. |\n| [PPTX #81](https://github.com/OpenPresentation/opf-pptx/pull/81): preserve valid inline layouts without standalone `$schema` | `c749c356b4fb5a5b5dfa77db8f1f7dd3c7daef63` | Initial explicitly dispatched [Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36541260948) passed on reviewed `a0d2779`; accepted tree is identical. Automatic PR CI had not started after retargeting. Postmerge acceptance is separate. |\n| [Site #46](https://github.com/Data-Advantage/openpresentation-site/pull/46): TypeScript 7 update | Closed without merging; branch retained | Existing prebuild regression harness uses compiler APIs absent from the candidate. The failure is not evidence that Next.js categorically lacks TypeScript 7 support. |\n\nThe dependency merges preserve package versions and the Node 24 default. Their\nCI checks source, packed packages, and the separately pinned registry consumer.\nThose are different evidence sets. Editor CI still uses older immutable sibling\npins; the successful runs do not prove the entire current font-program graph or\npublish the dependency updates.\n\n## Repairs and integration holds\n\n| Work | Reviewed finding and next step |\n| --- | --- |\n| [Core #144](https://github.com/OpenPresentation/opf/pull/144), merged into pending [#128](https://github.com/OpenPresentation/opf/pull/128) | Accepted correction `f8ca179488ee96c8466303bac06ea2dbf30502a0` has the exact reviewed tree. Snapshot writes could attribute dirty local or live URL bytes to a clean Git commit. The repair requires a clean local source for writes and makes URL/dirty comparisons read-only. Both original CI workflows passed on `fd3d6534dbc784554abba1b55c511df70a3ad8d5`, including ten provenance controls. The external gallery comparison was **skipped** because its checkout token is absent; a green job label does not establish that comparison. Integrate with current main and the [gallery publisher #46](https://github.com/Data-Advantage/pptx-gallery/pull/46) before accepting the catalog pair. |\n| [Application #57](https://github.com/Data-Advantage/pptx-dev/pull/57) | Separate the existing SDK/CLI validation commands into required CI steps. The prior combined Windows step could report success after an earlier SDK/CLI compiler failure. Original fresh checks on `5f863300392ba4adc17c947e23c9606623b4fc12` did not start because of account billing/spending restrictions. This repair has not passed Windows CI. |\n| PPTX [#78](https://github.com/OpenPresentation/opf-pptx/pull/78), [#79](https://github.com/OpenPresentation/opf-pptx/pull/79) and separate repairs [#80](https://github.com/OpenPresentation/opf-pptx/pull/80), [#81](https://github.com/OpenPresentation/opf-pptx/pull/81) | Review exposed stale media provenance after native edits and malformed recovered layout records. The original branches advanced during the review, and #79 merged as `0424d5617e2ff2454c61a4dc8622e355cae03e17`. Keep #80 separate while the media branch advances. #81 subsequently merged the remaining compatibility correction: valid inline layout records may omit the standalone `$schema` identifier. Its public API regression validates and renders the input, then checks exact reimport with and without the document tag. Caption whitespace and current-link fallback still need coordination with #78. Portable tests are not desktop Office evidence. |\n| Renderer [#42](https://github.com/OpenPresentation/opf-render/pull/42) and PPTX [#76](https://github.com/OpenPresentation/opf-pptx/pull/76) | Existing chart tests passed while public API controls exposed a renderer scale hang for valid finite extremes, scientific-notation parsing disagreement, missing-value disagreement, and scatter row-label loss. The independently reviewed scale repair [#46](https://github.com/OpenPresentation/opf-render/pull/46) merged into draft #42 as `062e56c4b38fc1bac4ab1be8e4a8ad7a4741bc80`, with the exact reviewed tree and fresh package/browser CI passing. It passes 36 finite-geometry controls and eight explicit overflow rejections; 468 ordinary SVGs remain byte-identical. Four synthetic rasters were reviewed: data geometry is meaningful, while extreme scatter labels overlap and one title is clipped. The chart pair still needs semantic fixes, current-base integration, and visual acceptance; passing old tests does not close these findings. |\n\nData-Advantage's recent Actions failures occur before test steps start. GitHub\nreports a payment failure **or** a spending limit restriction; the observed\nannotation does not distinguish them. An account administrator must restore\nexecution before fresh required checks can pass. Do not classify these runs as\ncode-test failures or use successful preview builds as a substitute.\n\nThe catalog correction also passed a separate read-only comparison against the\nclean exact gallery commit pinned by #128. Its [local receipt](evidence/pr-review-20260929/catalog-local-comparison.json)\nrecords the source binding and existing validation dependency installation.\nThat result does not change the external CI step's skipped status or establish\nintegration with current main.\n\n## Remaining acceptance\n\n1. Finish and independently review the current source repairs, reconcile active\n branches, and run fresh CI against each accepted head. Keep the five geometry\n drafts together: core #94, renderer #27, PPTX #42, editor #25, and site #40.\n Do not merge the site half alone.\n2. Complete the public application's remaining source-preservation and ordinary\n preview/undo paths. The September 21 canonical result remains 34/39; the\n Windows browser font-readiness result remains 38/39. Smaller later controls\n do not replace those unfinished suites. The preset Undo-all proposal remains\n unapplied; its earlier approval hold is not evidence of a completed repair.\n3. Complete the font-program matrix and native work against its stated criteria.\n Merged source does not prove the full gallery catalog, all scripts, or native\n PowerPoint fidelity. Existing parity counts are dated measurements, not a\n current overall completion percentage.\n4. Before another release, verify the complete candidate package set through\n fresh installed-package, browser, and cross-OS CI acceptance; review visual\n changes; then publish and verify exact registry artifacts before updating\n consumers and production. Record those steps separately from source merges.\n\nThe Windows owner is publishing a new inventory checkpoint in [core #145](https://github.com/OpenPresentation/opf/pull/145), including its own program-tracker updates. That evidence PR is independently owned; this review does not replace its audit or mark its native criteria complete.\n\nThe remote Windows supervisor retains sole desktop Office and native font\ncontrol. Coordinate ownership before changing its active branches. This task\nmakes no Office calls and does not claim a new native pass. The native tab gate\nstays **0.02pt**, the renderer gate stays **0.1px**, and native `p:hf` remains\nroadmap work. Physical glyph identity, native font allowlists and embedding\nremain explicit compatibility gates.\n\n## Resume\n\nFetch and inspect current branches before acting: the PR list changes while\nthe Windows owner works. Fast-forward clean primary checkouts without replacing\ndirty work. Do not assume checked-in source, existing build output and installed\npackages match; rebuild or install the exact source graph selected for a check.\nKeep the original failed and skipped evidence alongside subsequent results.\n"
116
+ },
87
117
  {
88
118
  "slug": "handoff-mac-owner-2026-09-10",
89
119
  "file": "docs/handoff-mac-owner-2026-09-10.md",
90
120
  "title": "Mac owner checkpoint \u2014 10 September 2026",
91
121
  "markdown": "# Mac owner checkpoint \u2014 10 September 2026\n\nRuntime update (September 10): use **Node 24 only** for new development and verification; see [migration instructions](migrations/node24.md). Historical Node 20/24 results and commands below describe prior checkpoints. Keep distinct browser/OS/native gates and the existing Office recovery prerequisite.\n\nThe active goal covers the full OpenPresentation ecosystem, with font/layout reliability before vector PDF, then SVG/Mermaid, followed by coordinated new releases and public-site verification. This checkpoint does not complete that goal. Read the full [8 September handoff](handoff-2026-09-08.md), [10 September wrap-up](handoff-2026-09-10-wrap-up.md), [ecosystem objective](plans/ecosystem-objective-2026-09-09.md), [font roadmap](plans/font-roadmap.md) and [text placement plan](plans/text-placement.md).\n\n## PR backlog and current state\n\nThe seven-repository audit found only core [PR #60](https://github.com/OpenPresentation/opf/pull/60) open initially. Its TypeScript 7 compatibility changes were reviewed, updated to current main and tested on the exact updated head. Node 20/24 coordinated CI, Mac/Windows portability and Bugbot passed. Local TypeScript 7/core tests and clean packed TypeScript 5.9/7 NodeNext/Bundler consumers passed. Merge: `e882f357cd7860b3e0905fdf0b2fb3ff8c2464d8`; issue #41 is closed.\n\nThe separate Windows agent subsequently opened PPTX [PR #17](https://github.com/OpenPresentation/opf-pptx/pull/17), retaining the owned verifier's process handle so Windows PowerShell 5.1 preserves its exit code. The exact head `95f5b9e0387dabb467a26d0fbbbdbe0171439a55` passed Linux/Windows Node 20/24 and Bugbot. Reviewed and merged as `24e7b2f12462df246e35a8a714a47923ecbd2bfe`. The [GitHub coordination handoff](https://github.com/OpenPresentation/opf-pptx/pull/17#issuecomment-5623170599) requests immutable native findings and preserved failures.\n\nThe initial audit found no open Dependabot security alerts in core, renderer, PPTX, editor or the three sites. Published versions remain core 0.9.0, CLI 0.7.0, renderer/PPTX 0.7.0 and editor 0.6.0. All three public origins returned HTTP 200 with successful production deployment records. That is a state check, not fresh deployed workflow certification. No npm version, release plan or deployment changed in this checkpoint.\n\n## Verified font preparation\n\nRenderer [PR #13](https://github.com/OpenPresentation/opf-render/pull/13), commit `91fe43e25dca9e4f04a2a18b1c6ee74e7e3d2863`, adds `prepareNodeFonts` and an immutable manifest for 33 existing open faces/eight license notices. Exact package versions and file/notice hashes are checked before use. Shared options carry measurement, embedded SVG bytes and raster paths; system fonts are disabled. Visual substitutions remain explicit and authored documents are preserved.\n\nThe old raster default omitted semibold, italic and bold italic from the nine-face base registry. A direct probe preserves the three failing outputs and shows all nine matching after correction. All 143 changed corpus slides were inspected in 18 paired sheets and six at full size. The separate baseline preserves all 662 unchanged hashes and identical source. Complete renderer suites/805 slides pass under Node 20.20.2 and 24.21.0. Nine exact base faces pass actual offline Chromium loading and advance checks; accepted-text and rich-spacing browser checks also pass. [Portable renderer evidence](https://github.com/OpenPresentation/opf-render/tree/91fe43e25dca9e4f04a2a18b1c6ee74e7e3d2863/docs/evidence/font-preparation) includes failures, paired images, hashes and reproduction instructions.\n\nCore's coordinated font harness now passes the same preparation options through pagination, editor composition, SVG/PNG and PPTX. Fresh candidate installations verify source preservation, edit/undo, rendering, editable export and heading reimport. The new declaration consumer checks the shared API and immutable catalog. TypeScript 5.9 and 7 pass. Historical registry harnesses remain version-pinned and do not call unpublished APIs.\n\nBoth Node 20 and 24 Mac installed-browser runs pass seven suites: canvas, rich text, layout, blocks, lists, creation and styled tables, totaling 230 assertions and eight trusted interaction scenarios. The initial Mac styled-table run failed because `Control+End` left all text selected. A direct Chromium control reproduced that behavior; `Meta+ArrowDown` collapses the selection at the end. The harness now uses the native platform shortcut and asserts the selection before typing. The original failure screenshot/log remain in [Mac evidence](evidence/mac-font-preparation/README.md). Product editing behavior was not changed.\n\nFont preparation merged after successful exact-head review and CI: renderer #13 as `e4d0dc0070616bc3b63a10124ce92cb4aff40e21` and core #62 as `028178311dc7f29667086809c83eb09405cefc4a`. No release occurred.\n\n## Merged physical font variants\n\nCore adds optional `FontFaceSelection` metadata to `TextStyle`. Renderer [#14](https://github.com/OpenPresentation/opf-render/pull/14) selects exact static weights through preferred-family grouping and preserves the physical legacy-family style flags. PPTX [#20](https://github.com/OpenPresentation/opf-pptx/pull/20) consumes those flags across measured editable payloads. Previously Roboto 500/600/800 selected neighboring 400/700 files; SemiBold/ExtraBold could receive incorrect synthetic bold flags. All six reproduced selection/style discrepancies are corrected in the new source probes. Explicit custom namespaces, duplicate/ambiguous guards and providers without metadata retain documented behavior.\n\nSource tests cover all nine base faces and seven payload slides on both Node runtimes. Actual offline Chromium advances match within 0.1 pixels; full renderer/converter Node 24 suites pass. The 805-slide base-font checkpoint stays unchanged. Seven complete before/after payload pairs were inspected. Fresh candidate checks repeat the physical-face tests from installed tarballs and verify shared layout, edit/undo, raster output, editable export and reimport. The [candidate evidence](evidence/mac-font-variants/README.md) records package/browser results and the separate native evidence review.\n\nPhysical variant changes merged after exact-head CI and review: core #64 as `a3ad7053e1bd6b7ba7ee8c98cc679caed741c433`, renderer #14 as `44ff76fe19ae9de80d2aaa962aa7e43fabce02bc`, and PPTX #20 as `cb297be6038b4ec6a84761693b6a7c9e4a4f518d`. The current coordinator pins the merged renderer, PPTX `8bf8aab4a69e2f89b489ff64cde2ae6b7c46de7e` (including the quote-role fix), and editor `6e0b7d2f1b4369fc0a228391cf36e913e05188f8`. These source pins remain separate from historical registry verification refs. Versions and `release-plan.json` still describe the published set.\n\n## Remaining work and ownership\n\nThe Windows agent owns bounded native harness work and Office evidence. The user reviewed recovery and resumed Office activity on that computer. PPTX #18 bounds accepted-text workers and guarantees parent-owned temporary font cleanup; #19 bounds the six metric decks and preserves existing fidelity gates. Core [#63](https://github.com/OpenPresentation/opf/pull/63), exact evidence `e862a5f48bbb9e34aa6e5115922848292e23797f`, contains 1,585 byte-verified files. Both Node runtimes pass 24 accepted-text cases, 144 editable lines, 72 exact original/saved/edited imports, 24 equal save/reopen PNG pairs and 96 original-text ink masks. The Mac independently checked those masks and records. The result is scoped to the recorded graph and does not identify every native glyph's font file.\n\nThe original retained baseline on both runtimes reproduces eight metric tab outliers (maximum 0.0673828125 points) and one portrait/right Latency ink overflow at x=497 beyond 496.8. Their 144 imports each pass; character bounds, mask coverage and collisions pass. The Mac independently decoded 96 complete metric slides and 276 isolated masks, checked raw tab arithmetic and import hash bindings, and reproduced the failures. Precise saved DrawingML tab coordinates do not establish why COM/native shaping differs. The Mac owns the shared runtime investigation; avoid duplicate Windows runtime edits.\n\nCore #65/PPTX #21 completed the eight-workbook native chart edit matrix on both runtimes. Exact evidence `a4e7e9b9b5810144b1f116d45a92bc71e1484c78` has 2,289 verified files. Independent Mac review executed 384 imports and 384 separate cache parses, checked live edits, all 128 original/reopened PNG pairs and all sixteen selected-slide raster changes. PowerPoint's loaded pie category getter returns null even in an independently native-created control; this is explicitly unavailable, while live edits and cached XML provide separate exact observations. The original dialog/crash/stale-cache failures remain retained.\n\nCore #66 merged as `626bc8c80495733264a692401e6a8c96663dd62f` after nine checks passed. Its bounded metric layout uses accepted outlines and raster clearance while preserving literal source/tabs, physical font selection and readability floors. Both Node runtimes pass 96 actual offline browser cases; the width-only control also passes and does not reproduce the native Calibri failure. Fresh Windows evidence in core #68, exact `5d39217f0716453bf3762057852006b4787de73b`, independently confirms that the portrait/right Latency ink overflow is gone. The Mac checked 96 full native rasters, 276 isolated masks and 288 actual original/saved/edited imports. Six leading-tab discrepancies remain per runtime (max 0.0226745605469pt against 0.02pt); the full fidelity gate still fails. PPTX #23 corrects only the verifier's accepted-anchor assumption; tolerances and independent ink/source gates are unchanged. Core #68 merged as `fb56cd247091f45a739eb3e752126db9e69fbe4d`.\n\nCore #67/PPTX #22 retain bounded native table/code/quote checks, exact evidence `2024141c116e81608d3c6632b02c7bcbcd2a2bcc` with 2,646 verified files. Tables/code pass their scoped gates. Stronger quote comparison exposed 36 portrait first-body-line-to-subtitle failures. PPTX #24 fixes inference beside complete roles and adds missing tags to default estimated-font headings without changing native geometry. It merged as `8bf8aab4a69e2f89b489ff64cde2ae6b7c46de7e` after all Linux/Windows Node 20/24 and Bugbot checks. Independent Mac runs of 108 table, 48 code and 72 quote imports pass on both Nodes; all 36 prior role failures are retained and now corrected. Six actual browser suites and sixteen loaded/default regression cases pass. Quotes still reimport as generic text lines, not semantic quote payloads.\n\nThe [Akasia assessment](evidence/akasia-assessment/README.md) records exact v0.0.2 open files and public upstream metric agreement across all twelve styles. It retains 14 missing reference codepoints and a Black Italic decomposed-mark Fontkit/Chromium difference of 0.421875px at size 32 on both Nodes. A source/NFC cluster control isolates the difference. Three full slides were inspected with existing sparse layout/contrast limitations explicit. No proprietary reference binary comparison, new mapping or font pack is shipped; Akasia remains experimental.\n\nCore #69 merged the Akasia assessment and metric follow-up. Core #70, exact evidence `28ae661a606d0a70ff39d98aac32e84240dbbb23`, adds fresh native quote runs after PPTX #24 and native-created tab controls from merged PPTX #26. All 182 files were checked against immutable Git blobs; the Mac independently reimported 72 quote slides on each supported Node runtime. Exact current body/footer order, multiplicity and title edits pass. The 0.02pt tab gate still fails in the native-created control; no offset/tolerance workaround is justified by this evidence.\n\nThe natural-rich-text candidate closes the estimated fragment-gap defect, preserves measured origins and adapts editor selection/caret mapping to traced SVG spans. Core harness `0f937d3ee94644ee3df312b7dbbf36b6efe27e70`, renderer product `57be896e761af102961521acd7dd6104273296bf` and editor product `eeaee72f011ccb3d36259236d46fc9a4124a1eed` define the tested source graph. The [portable evidence](evidence/mac-rich-flow/README.md) records the 30-case browser checks, source edit/undo and clean installed-package checks. All 188 raster changes were visually reviewed with six full-size pairs; 617 hashes remain unchanged. The separate rich-flow checkpoint preserves historical baselines. This increment remains unpublished.\n\nRenderer #15 merged as `c92198b0d0acc94892385a8594fe2879461d64b0` and editor #12 as `a0aa911188f8a6364b66c7a19cece64580c1d9f8`, with Node 20/24 CI and independent review passing. The follow-up source graph uses renderer `6520f091a95aa95be5993580adf5df567eb439c8` and editor `f0871842088d2180ed7de606c379d7921e7fbf0e`. It resolves Linux measured SVG advance drift through geometric precision and explicit accepted horizontal advances, keeping raw font metrics distinct from constrained output. Independent review also fixed inherited code-role guards for traced spans; real text-node selection now covers code body/filename/language. Core #71/PPTX #27 retain nine native output family/style matches and blocked image controls. The Mac verified all 389 files, 28 fresh font-slide imports on each Node, and all seven full-size PDF rasters. Native image acceptance remains blocked pending reviewed Office recovery; no further COM retry or cleanup claim.\n\nCore #72 merged the rich-flow coordination and native follow-up as `804c6d362fd175940b8dd3170976ae6ffe87ed8c` after all supported-runtime checks and independent review passed. The subsequent scalar whitespace candidate is described in [its plan](plans/plain-text-whitespace.md) and [portable Mac evidence](evidence/mac-source-whitespace/README.md). It preserves spaces, tab segments and exact hard-break source ranges through fitting, SVG and editable PPTX reimport, fixes blank scalar selection and preserves untouched mixed endings across separate editor input events. A single wholesale replacement keeps the existing replacement-range newline policy. The 805-slide candidate changes 279 hashes, all reviewed in 35 paired sheets plus six full-size pairs; 526 are unchanged. Historical baselines and failures remain retained. The candidate is unpublished and does not close native Office or general visual-quality gates.\n\nContinue the font roadmap: native follow-up for physical variants, real measurement integration in user workflows, text features, bounded repair/readability, diagnostic paths, multilingual shaping/coverage, Akasia/Aptos evaluation and native portability. Estimated rich-run gaps and scalar whitespace loss are corrected by the current candidates; sparse cards, missing marker glyphs and unresolved assets/data remain visible; the 805-slide hashes do not certify quality. Keep optional font packs openly licensed with provenance and bounded downloads. No silent content loss or synthetic compatibility claims.\n\nVector PDF remains gated on font/layout acceptance; sanitized SVG and the complete Mermaid support matrix follow afterward. Publication must use new versions in dependency order, then clean registry workflows, installed-package browser tests and all three deployed sites. The current releases and prior failures must remain distinguishable from source candidates throughout.\n\n## Readability floor source checkpoint\n\nThe preceding whitespace PRs merged: core #73 `c51b02571eab6ee61d03560aa1f539072b2d381b`, renderer #16 `c7a7387f6e27cc21eeeec3015492afe289ec4ce3`, editor #13 `aea29cf6998127fda96a5cbcd6b608db4c4890e3` and PPTX #28 `4c75ff713a19dff072e8f3660c368499973b78ab`. The subsequent audit found no open PRs or Dependabot alerts across all seven repositories. The Windows coordination note is [posted](https://github.com/OpenPresentation/opf/pull/71#issuecomment-5625857758); its request remains conditional on user-reviewed Office recovery.\n\nThe next candidate fixes selected readability floors and bounds fitting to 65 candidate sizes, retaining source, run ratios and the prior relative shrink constraint. Core product is `ade2a4f0a8afa93f546a3b1e18f2ed23b84d0ac7`. [Portable Mac evidence](evidence/mac-readability-floor/README.md) records the reproduced failures, corrections, both runtime suites and fresh installed editing/undo/export checks. The independent 48-case SVG/PPTX size matrix passes from source and installed packages on both runtimes. Renderer #17 retains all 33 reviewed paired sheets for 262 changes, six full-size pairs and the exact predecessor. Editor #14 and PPTX #29 coordinate CI only. CI/review acceptance and merge status must be checked live; no package or site release has occurred.\n\nThe floor checkpoint covers scalar/rich/list/table glyphs and shared heading allocation. It does not close small template footers/page numbers, all chart/timeline internals, missing nested marker coverage, Auto arrange, general visual quality or native Office gates. Continue reliability before vector PDF and then sanitized SVG/Mermaid. The overall goal remains active.\n\n## Shared timeline source checkpoint\n\nReadability merged after all CI/review gates passed: core #74 `2aeb86012c15193dba78ae81be838c9d7e09ac67`, renderer #17 `53b90878b8a9610117ac5191ebfaaa549cc2f3ea`, editor #14 `f71c1cdb3e9e25c9a62e25b0e711792820e6545e` and PPTX #29 `13b2229e3172e48ee489e68cac8432471993d7a9`. Core exact head `82e5a86d597e99c5e3c032d8b3aea717fb0393e3` passed all nine checks, including coordinator run 34535898793; review comments were empty. A fresh seven-repository audit found no open PRs.\n\nThe new timeline implementation fixes core/renderer acceptance disagreement, omitted metadata and synthetic shorthand source paths. Product graph: core `cc4b5245ba1c197746a2c307b78ff58f45df02cc`, renderer `1e5cd95a76e356c09ae5da0c8604809a202b1f40`, editor `78184f161bdb243822f2b21699ee0ab759c4c7bd`, PPTX `6f06406d9615b35a66f39e6424b35b1d32a73052`. [Portable evidence](evidence/mac-shared-timeline/README.md) includes source and installed browser checks on both runtimes, exact current-native-text semantic timeline reimport, bounded search and strict overflow/pagination controls. The independently reproduced predecessor matches all 805 hashes; 83 candidate changes were reviewed in eleven paired sheets and six full-size pairs, with 722 unchanged. New timeline baselines are separate from prior checkpoints.\n\nThis checkpoint is unpublished. Verify live PR/CI status before claiming merge acceptance. Native tab tolerance still fails and image acceptance remains blocked pending Windows Office recovery. The narrow portrait browser control fits but remains heavily wrapped; Auto arrange and overall quality are not complete. Continue font/layout reliability before vector PDF, then SVG/full Mermaid, coordinated new releases and three deployed-site workflow checks.\n\nShared timeline is merged: core #75 `166cebd8d8af4a80cef3e06ece8e0f8f2d1cecb8`, renderer #18 `f863c9c9b234a36720e2500e19b0384e340f1cfb`, editor #15 `6a3b85aecd7ee96bbcd67d60e02a4c3e32ba93bb`, PPTX #30 `9646c0ddf6880dde1d41ecdf21446d347cd88bf4`. Final core head `c2379f870ee0eadabfcda2f738226bf8184451f3` passed all nine checks, including coordinator 34540948071; no core review comments. Final PPTX `d03e2daebaf9fb6c5d01a7461da22695da2bcb9f` passed Linux/Windows Node 20/24 and Bugbot (run 34540943584). Its guard-order review was fixed before merge. The editor blank-field finding was disproven by its existing generic source-text fallback and actual pointer/bounds controls; the [additional blank-when evidence](evidence/timeline-review-follow-up/README.md) is retained. Both runtime editor CI passed and the finding was resolved before merge. A fresh seven-repository audit returned zero open PRs.\n\nThe next [shared header/footer plan](plans/shared-furniture-layout.md) begins on `codex/shared-furniture-20260910`. Read-only Node 20/24 wide/portrait controls reproduce fixed 13px furniture at selected floors 16/32 and missing authored header/footer words in PPTX. They remain in `artifacts/furniture-predecessor` and the source probe/output files in `/private/tmp/opf-furniture-gap-before-node*.json`. No furniture product changes have been made yet. The core branch is based on the merged timeline checkpoint. The exact timeline core raster predecessor archive is retained at `/private/tmp/opf-timeline-predecessor.tgz` (SHA-256 `ad60806239f3aa3d6a9e0993a74d7781225a75998736213f019e34eb7f78a9bd`), with its original candidate manifest; the accepted timeline raster corpus remains available. Published versions, release plans and deployments are unchanged. The overall goal is still active.\n\nThe final [Windows handoff](https://github.com/OpenPresentation/opf/pull/71#issuecomment-5626767382) records the timeline/readability merge graph and keeps new native work conditional on reviewed Office recovery. [Portable header/footer predecessor evidence](evidence/mac-shared-furniture-predecessor/README.md) preserves the next defect before changes. All four active runtime checkouts now use `codex/shared-furniture-20260910` based on their merged timeline checkpoints.\n\n## Shared furniture implementation checkpoint\n\nThe first source implementation is committed: core `864b73e356c45211a28f3ca5afe0702b55868fa4`, renderer `4e11b9557b589e3a9d04c7c97b4a0c66bce83d94`, editor `e0c817fb372a5639f9fd1c829e85bf21fb1ba241`, PPTX `d06bc9c72aef16fdbe345704309f53ed97321417`. The preceding statement that no furniture product changes exist is historical. [Portable draft evidence](evidence/mac-shared-furniture-draft/README.md) records this exact candidate, its failures and current limits.\n\nCore resolves inherited/local/disabled furniture, preserves literal strings and metadata sources, measures accepted parts at the selected floor and reserves body space (`grid-score-v9`, `furniture-flow-v1`). Pagination evaluates final output page numbers and separates optional repeated metadata mappings from its existing body slices. Core's 507 tests and legacy pagination pass on Node 20/24. Thirty-two offline browser cases per runtime pass source editing, empty fields, generated-value selection guards, dates and undo/redo; all four reviewed full-size screenshots match across runtimes. Sixteen PPTX exports per runtime match accepted text boxes, font sizes, source lines and fitted images; all sixteen outputs are byte-identical across runtimes. Existing editor and converter suites pass on both runtimes.\n\nSemantic furniture reimport is **not implemented**. PPTX currently draws accepted parts with stable shape names and preserves blank lines, but has no furniture provenance tags. Continue that work before creating the coordinated PRs or claiming a complete author/edit/export/reimport workflow. The plan identifies standard common-slide customer data as a possible topology carrier for empty/false definitions, to assess and test without stale source words. Missing/duplicate/changed groups, current native text/images, generated metadata disagreement and inheritance/overrides need explicit controls.\n\nRenderer Node 24 non-golden checks pass. Its old timeline baseline correctly reports **657 changed / 148 unchanged** slides with an unchanged source digest. The candidate manifests are in `opf-render/artifacts/furniture-golden-after`; this initial run did not request PNG artifact retention. Regenerate the same candidate with `OPF_GOLDEN_ARTIFACTS=1` or `--update` into a distinct review directory, retain/verify predecessor PNGs, then inspect all changes and full-size pairs before promoting any new baseline. The old timeline baseline remains untouched. Clean candidate package/installed workflows, coordinated CI, PR review and merging remain pending. The large-font portrait browser specimen wraps the organization heavily despite passing bounds.\n\nA fresh seven-repository audit still returned no open PRs. The latest three comments on core #71 contain no new Windows recovery evidence; the existing tab failure and blocked image state remain unchanged. No COM retries occurred. Versions, registry releases, sites and the full-goal acceptance status remain unchanged; continue reliability before vector PDF, then SVG/full Mermaid and coordinated publication.\n\nThe full candidate PNG corpus has now been regenerated into `opf-render/artifacts/furniture-golden-review`: all 805 PNG hashes match its manifest, and that manifest exactly matches the retained first comparison. Paired visual review is still pending. The four source commits and evidence checkpoint `abf3e27563364b4a6e1a095558740be8bd0da85c` are pushed to their matching furniture branches; no furniture PRs exist yet.\n\nConcurrent working-tree edits to `docs/plans/ecosystem-objective-2026-09-09.md` and `docs/plans/font-roadmap.md` add an accepted Node 24-only runtime maintenance milestone after current in-flight checks and before release. These edits were discovered after the source checkpoint and left intact and unstaged. The current Node 20/24 checks are finished; follow the updated roadmap for subsequent work rather than starting more duplicate Node 20 runs. Audit and align the four packages/CLI, three sites, CI/packaging, clean installation, tooling and Windows coordination on Node 24 while retaining distinct browser/OS/native gates. Preserve historical evidence and published artifacts. This newly observed roadmap does not mean the engine declarations or active CI matrices have been migrated yet.\n"
92
122
  },
123
+ {
124
+ "slug": "handoff-runtime-2026-09-29",
125
+ "file": "docs/handoff-runtime-2026-09-29.md",
126
+ "title": "September 29 runtime and release checkpoint",
127
+ "markdown": "# September 29 runtime and release checkpoint\n\nThe delivery goal remains open, and work has resumed.\nThis checkpoint separates accepted source, installed-package checks, native\ncompatibility and publication. No package was published by this review. Web merges\nmay trigger existing deployment automation; local acceptance does not establish\nproduction acceptance.\nOfficial npm latest metadata checked on September 29 still reports the\nSeptember 17 train: core 0.11.0, CLI/render 0.9.0, PPTX 0.9.1 and editor 0.8.0\non Node 24.x. See the [published compatibility\nmatrix](compatibility-matrix.md) and [earlier checkpoint](handoff-2026-09-29.md).\n\nThe earlier [notes/scalar whitespace repair, PPTX87](https://github.com/OpenPresentation/opf-pptx/pull/87),\naccepted as `170bb8755ba07c1e81b0a6a0c98b3e7d84f06eb0`, passes 123 controls in\nsource, registry-packed and coordinated installed contexts. Original Linux and\nWindows premerge CI passed. The first automatic postmerge run was canceled on\nboth platforms; the distinct later run's retained snapshot records Linux success\nand Windows pending. Neither is relabeled a complete postmerge pass. It preserves\nnotes, description and native\nscalar characters. Supported ordinary body formatting is now accepted separately\nin PPTX90 below; original source-shape reconstruction remains open. No new\npackage version is implied.\n\n[Core155](https://github.com/OpenPresentation/opf/pull/155) accepted the six\nrenderer-absent furniture groups and coordinated UTC fixture, with original\ncore/ecosystem CI passing. Two synthetic edited copies differed only in caller\nZIP timestamps; their original whole-byte comparison failure remains retained\nalongside equal member contents. [Core156](https://github.com/OpenPresentation/opf/pull/156),\naccepted as `b3c8fbf762d975bd2e7d65d56b7e9838c94ae2e7`, adds the installed\nportability foundation. Its original macOS missing-Chromium setup failure was\npreserved and the browser installation moved before first use; all corrected\npremerge jobs passed. Installed Linux/macOS/Windows and strict compiler controls\nremain distinct from FF-09 font switching, physical-font/native acceptance and\nfinal unmodified release tarballs. The postmerge snapshot retained Linux/Windows\npending results without relabeling them as passes.\n\n## Local acceptance during the Actions credit shortage\n\nOn September 29 the owner explicitly authorized local tests and reviewed PR merges\nwhile Actions credits are unavailable. Fresh appropriate local acceptance is now\nsufficient for these merges; zero-step billing denials remain recorded as such.\nNo workflow protection, test assertion, native tolerance or release gate was\nrelaxed. Passing local tests is not a cross-platform, production or desktop\nPowerPoint result.\n\nThe following accepted trees were independently reviewed and verified after\nsquash merge. [Compact merge receipts](evidence/local-acceptance-merges-20260929/README.md)\nrecord each exact head, base, tree and accepted commit.\n\n| Accepted PR | Change and local evidence |\n| --- | --- |\n| [App52](https://github.com/Data-Advantage/pptx-dev/pull/52) | Nano ID 6; 704 unit tests, 13 standalone controls, 43 browser cases, SDK/CLI and app build/type checks, zero audit findings. |\n| [App57](https://github.com/Data-Advantage/pptx-dev/pull/57) | Six SDK/CLI workflow commands now have separate required steps, preserving Windows exit status. Structural negative controls and the same six local commands pass; no new PowerShell result is claimed. |\n| [PPTX89](https://github.com/OpenPresentation/opf-pptx/pull/89) | Explicit native underline at four exporter sites. Source/current-installed controls, packed regressions and reviewed export comparisons pass; original failures and the published-core furniture limitation remain separate. |\n| [Gallery37](https://github.com/Data-Advantage/pptx-gallery/pull/37) | Dependency update with fast-uri security correction; 117 units, 7 browser cases, build/type/registry/editor checks and zero audit findings. |\n| [App58](https://github.com/Data-Advantage/pptx-dev/pull/58) | Exact-source preset history and synchronous accepted-replacement guards; 737 units, 13 standalone controls, 49 browser cases and build/type checks. |\n| [Site48](https://github.com/Data-Advantage/openpresentation-site/pull/48) | Dependency/security update plus compatible Lezer common deduplication. The first green browser run lost syntax highlighting; the corrected candidate passes the real highlighting regression and 27 browser cases, with visual review and zero audit findings. |\n| [App51](https://github.com/Data-Advantage/pptx-dev/pull/51) | TypeScript 6 with explicit SDK/CLI Node types, declaration-only tsup accommodation and one source-bound test-helper correction; 737 units, 13 standalone controls, 49 browser cases, SDK/CLI and app build/type checks pass. |\n| [Gallery48](https://github.com/Data-Advantage/pptx-gallery/pull/48) | Selected detail-page, registry and editor JSON agree, including legacy hash links; 135 units and 7 original plus 5 scoped browser cases pass. Supersedes closed Gallery45 without merging Gallery44. |\n| [App56](https://github.com/Data-Advantage/pptx-dev/pull/56) | Dependency update; 737 units, 13 standalone controls, 49 browser cases, SDK/CLI and app build/type checks, zero audit findings and eight bounded HTTP MCP limit cases in CJS and public ESM pass. Scoped visual differences were reviewed; live authentication/provider and production acceptance remain separate. |\n| [PPTX90](https://github.com/OpenPresentation/opf-pptx/pull/90) | Supported current native body/list formatting survives import. All 16 portable stages, 42 body and 123 notes controls across source/registry-backed/installed contexts, seven source browser suites and 108 installed browser checks pass; six previews reviewed. |\n| [Gallery49](https://github.com/Data-Advantage/pptx-gallery/pull/49) | Metric values and labels remain readable; financial KPIs sit above the original chart. 144 units and 7 original plus 5 scoped browser cases pass, including edit/undo and all 15 pairs in native export. Four metric previews reviewed. |\n\nAll application/gallery browser counts above are single runs with zero retries.\nSite48's original visually failing run is retained separately from its corrected\ncandidate. Root and delegated reviews retain bound source, original failures,\nlogs, screenshots, downloads and browser traces in the private task archive;\ncompact public receipts are an index, not a replacement for raw evidence.\nNo new npm train or manual deployment is included.\n\nPPTX90 intentionally lets some ordinary untagged body/list values import as rich\narrays instead of strings, preserving current native runs, fields, breaks,\nwhitespace and supported styles. Heading inference and tagged recovery stay\nseparate. Exact current native text/styles replace the affected scalar test\nexpectations; their original failures remain retained. The portable native-quote\ncomparator keeps exact characters and block structure while allowing schema-valid\nrich values. This does not reconstruct original cross-shape authoring boundaries,\nsource run identities or master/layout inheritance, and does not complete FF-09\nor native/font compatibility. The only post-acceptance source change removed one\nextra EOF newline from two identical modules, with byte, syntax and parity checks.\n\nGallery49 isolates the metric repair from the held image recipes. It preserves\noriginal chart data, all image snippet bytes and visible missing-asset refusal.\nUnknown whitespace or ambiguous metric strings remain exact. This is bounded\nmetric/payload ordering, not universal lossless prose parsing; quote reimport and\nthe rest of FF-30 remain open.\n\nApp51 does not fix the existing standalone `@pptx/cli` declaration dependency:\na CLI-only tarball cannot resolve its SDK type dependency, also on the preceding\nTypeScript 5.9 graph. The co-installed SDK/CLI consumer passes. Experimental\nbundled declarations caused incompatible private class identity and were rejected;\nstandalone package release remains a separate gate.\n\nGallery44 remains held despite 149 units and 7 original plus 6 scoped browser\ncases passing on its reviewed pair. Its five new image recipes change a visible\nmissing-asset export refusal into successful output with missing images and no\ndiagnostic; two yield white text on white. A detail-page note does not protect\ndirect editor links or external consumers. Gallery48 preserves the original\nclear refusal and does not claim to fix image fidelity. Gallery49 separately\nrepairs the reviewed metric wrapping; the unresolved image work remains open.\n\nThe published graph also still ignores all ten Gallery42 `socials: true`\nexamples: removing that flag leaves preview SVG and native slide XML unchanged,\nand reimport omits organization socials. Its future-release qualification is\naccurate, but it is not current functional feature acceptance. Gallery47's newer\nnumber/date field options and Gallery43 backgrounds likewise retain their\ncoordinated release requirements. Their branch proposals are not a shipped\npackage capability.\n\n## Accepted portable fixes\n\n[PPTX #82](https://github.com/OpenPresentation/opf-pptx/pull/82) preserves native\nchart cache positions and trailing gaps. Missing values remain null and actual\nzero and explicit empty labels remain distinct; malformed or excessive caches\nreject before dense allocation. Accepted commit\n`7fca9a2eb5088ee2325fb686d1ff84332af2b8d9` has the same tree as reviewed\n`1c60887793067d8c830a289d8bf0e3405f9749c2` and its original tested merge.\nOriginal [Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36546748615)\npassed, as did original accepted-main [postmerge CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36548215070). Each platform ran 44 public cache controls against a fresh packed\nconsumer and again in the source suite. All 36 shipped files bind to reviewed\nsource; Windows CRLF conversion is recorded explicitly.\n\nAn unedited numeric cache containing `1e21` or `1e-7` still imported as `121` or\n`1` after that fix. [PPTX #85](https://github.com/OpenPresentation/opf-pptx/pull/85),\naccepted as `2fa70d8b71be13d7053b295994c984e6478f761d`, reads complete signed\ndecimal exponent tokens without changing the shared exporter parser or workbook.\nExponent overflow and nonzero underflow refuse with the affected chart, series,\ncache and logical point path. All 44 prior cache controls plus 13 new groups\npassed in fresh installed and source scopes on both platforms in the original\n[CI run](https://github.com/OpenPresentation/opf-pptx/actions/runs/36561675960).\nAll 36 shipped files bind to the reviewed tree. Three representative exports\nremain byte-identical, including their embedded workbooks. Original accepted-main\n[CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36563260194) also\npassed on Linux and Windows at the identical reviewed tree. Both original\nartifacts passed digest and ZIP integrity checks; the 36 shipped files and all\nnine packed fixtures bind to that tree, with Windows line endings recorded\nexplicitly. Neither workflow runs desktop Office.\n\nMalformed/non-exponent parsing, exporter/workbook missing-value behavior,\ncategory disagreement, scatter labels and broader chart parity remain open.\nThe broad renderer/PPTX chart drafts cannot be accepted solely because these\nbounded import fixes passed.\n\nRenderer [#47](https://github.com/OpenPresentation/opf-render/pull/47), accepted\nas `c62b3f98a4ac98cdec8ffd28c035a17a04197396`, fixes finite extreme values that\npreviously produced infinite coordinates or `NaN` axis labels in column, bar,\nline and area charts. It preserves the ordinary arithmetic path. All 36 public\naxis cases passed against source and a fresh installed package, with all 20\nshipped files bound to reviewed source. The original\n[package CI](https://github.com/OpenPresentation/opf-render/actions/runs/36564292238)\npassed; its 805-slide, 126-deck golden and the six bounded ordinary SVG controls\nare unchanged. Ten local before/after images were reviewed. Very large scientific\naxis labels still wrap or clip, so this is arithmetic acceptance, not completed\nextreme-chart readability work.\n\nThe renderer's separate, report-only\n[platform run](https://github.com/OpenPresentation/opf-render/actions/runs/36564292334)\ncompleted successfully while still measuring five of five Source Serif Linux\nrows above the unchanged 0.1 reference-pixel limit (maximum 0.134625 px).\nThe corresponding macOS rows and 18 bundled-font controls per platform were\nwithin that limit. This diagnostic result does not close renderer issue #24 or\nthe native/font compatibility gates. The coordinated installed-package harness\nnow runs the original exponent and axis fixtures against accepted #85/#47;\nhistorical registry checks keep their original published-train scope.\n\n[PPTX #81](https://github.com/OpenPresentation/opf-pptx/pull/81), accepted as\n`c749c356b4fb5a5b5dfa77db8f1f7dd3c7daef63`, also passed its original automatic\n[postmerge Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36542244211).\nIts valid inline-layout regression ran in source; that older packed subset did\nnot yet include the layout fixture. Selected installed tests are not a complete\nfidelity gate.\n\n[PPTX #83](https://github.com/OpenPresentation/opf-pptx/pull/83) preserves current\nmedia caption characters across soft wraps and exact full-tag CR/LF boundaries.\nCleared captions use linked-text/empty-text fallback instead of inventing a URL\ncaption. Compatible older soft/end tags remain readable. Accepted commit\n`357171a5ceb798ccd550207ec365b08a694c5d6d` has the exact reviewed and tested tree\nof `08815c76ab9494cfad98e18149c003385306882b`, which integrates #82.\nOriginal [combined Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36548912840)\npassed with all nine installed fixtures, including 37 media groups, 11 layout\nintent groups and 44 cache controls. Original [postmerge CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36550576333)\nalso passed on both platforms, with all shipped files and fixtures bound to the\naccepted tree. Six bounded before/after media images were reviewed;\nfull-tag source fidelity, references-only semantic LF and ordinary native fallback\nhave distinct limits. This does not restore arbitrary native formatting/geometry\nor certify desktop Office. Owner #78 merged earlier; the owner closed #80\nunmerged, and its branch remains preserved.\n\nThe [compact receipts](evidence/runtime-pr-review-20260929/README.md) bind the\nearlier #81/#82/#83 records; #85 is recorded separately above. All source fixes\nabove remain unpublished.\n\n## Installed furniture checkpoint and wrapped-date follow-up\n\nCore [#150](https://github.com/OpenPresentation/opf/pull/150) accepted the current\nrenderer/editor/PPTX checkpoint and candidate-only formatted-furniture fixture.\nCore [#151](https://github.com/OpenPresentation/opf/pull/151) accepted this runtime\nhandoff. Their combined main at `ea32a63111c6d06be333e20fb31db22ab2d7b7a1` passed\nboth original [core](https://github.com/OpenPresentation/opf/actions/runs/36554712162)\nand [ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36554712113)\nchecks. The earlier #150 ecosystem push run was cancelled after #151 landed;\nits later skipped stages remain recorded as skipped, not passed.\n\nCore [#152](https://github.com/OpenPresentation/opf/pull/152), accepted as\n`de88f4e0607ef099f41c96331b1c353502ba7cec`, adds three installed-browser workflows\nfor wide/portrait decks and actual pagination. Six PPTX exports check the same\nexplicit host date and final slide total as composition and actual SVG text.\nThe fixture also checks title-footer hiding, exact source ranges, literal edits,\nundo/redo and a date-only update that leaves document history unchanged.\nIts original [core](https://github.com/OpenPresentation/opf/actions/runs/36555305435)\nand [ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36555305454)\nchecks passed, as did accepted-main [core](https://github.com/OpenPresentation/opf/actions/runs/36556931351)\nand [ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36556931527).\nBoth accepted-main runs bind the reviewed tree; core passed 707 tests. The three\noriginal CI screenshots were reviewed, and the postmerge images are byte-identical.\nThe initial local test-parser failure is retained separately. Its correction\nchanged no runtime code or field/source contract.\n\nA separate portrait `minFontSize: 32` (reference pixels) probe exposed an unedited roundtrip loss:\na current date spanning two accepted lines became static PPTX text without a\nwarning, then imported as a literal instead of `date: true` with its format.\n[PPTX #84](https://github.com/OpenPresentation/opf-pptx/pull/84), accepted as\n`f91fbf82f7407b669ae159435aa4d7925ac51832`, now diagnoses dropped native field\nranges and recovers unchanged supported wrapped-date intent through bounded full\nprovenance. All 41 other files and 19 directory entries in the original probe remain\nbyte-identical; only two furniture manifest tag files change. Current edited or cleared text wins.\nOld unmarked, references-only and provenance-off exports remain conservative.\nThe PPTX words remain static: this does not restore native live-date refresh or\narbitrary formatting/geometry.\n\nThe repair passed 20 focused source and fresh installed controls, including the\noriginal package comparison, plus its full portable suite. Original\n[Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36556325988)\nand original accepted-main [Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36557698682)\nboth passed 19 new source cases per platform against that workflow's recorded\nolder sibling graph. The accepted commit and all shipped files bind to the\nreviewed tree. These passes are distinct from the current coordinated graph.\nCore [#153](https://github.com/OpenPresentation/opf/pull/153), accepted as\n`7fdd39e39a4712c052b08f56f5a4dd4d9a625ab2`, uses accepted #84 with its recorded\nrenderer/editor pins, adds 19\ninstalled wrapped-date mutation controls and expands the\n[browser fixture](../scripts/test-furniture-fields-browser.mjs) to four workflows.\nThe [compact acceptance record](evidence/wrapped-furniture-20260929/README.md)\nbinds fresh local package checks, 805-slide goldens, 707 core tests and all four\npassing browser workflows. Both original PR workflows\n([core](https://github.com/OpenPresentation/opf/actions/runs/36559866383),\n[ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36559866319))\nand original accepted-main workflows\n([core](https://github.com/OpenPresentation/opf/actions/runs/36561369174),\n[ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36561369076))\npassed every step. The accepted tree matches the reviewed tree; 19 installed\nwrapped controls and all four browser cases passed. All four reviewed screenshots\nand eight exported PPTX files are byte-identical between those original premerge\nand postmerge runs. The wrapped date is legible; the 805-slide golden is unchanged.\nThese candidate results are not a published or native compatibility claim.\n\n## UTC ZIP dates and the renderer-absent candidate gate\n\nCore [#154](https://github.com/OpenPresentation/opf/pull/154), accepted as\n`4bdaa5900ecd6a647d491c0322bfbf5bc71f59dc`, passed its original accepted-main\n[core](https://github.com/OpenPresentation/opf/actions/runs/36568416311) and\n[ecosystem](https://github.com/OpenPresentation/opf/actions/runs/36568416369)\nworkflows. That checkpoint binds the accepted chart fixes, 19 installed\nwrapped-date controls, four installed furniture browser workflows and unchanged\n805-slide golden. Its eight Linux PPTX exports match the prior Linux run.\nThe separate local-versus-Linux comparison found identical uncompressed content\nacross 714 ZIP members, with only timestamp metadata differing; it did not pass\nwhole-file cross-timezone determinism.\n\n[PPTX #86](https://github.com/OpenPresentation/opf-pptx/pull/86), accepted as\n`373dfa39688e787d861202e7a8069ebff7e8be36`, repairs explicit `zipDate` handling\nusing UTC calendar fields in both PPTX and embedded workbook archives. The tree\n`2405fa1b86ac1abc034f14ab47e01bd8d38a56c2` matches reviewed head `5865743`.\nOmission/undefined retains established fixed 1980 output; `timestamp` remains\nthe separate core-property XML option. Ambiguous, invalid and out-of-range\nexplicit dates now reject with `invalid-zip-date` at `options.zipDate`, an\nintentional unreleased input-contract tightening. Original\n[Linux/Windows CI](https://github.com/OpenPresentation/opf-pptx/actions/runs/36570457498)\npassed the public timezone fixture in source and fresh packed contexts. Whole\ncontrol-PPTX bytes agree across those platforms within each recorded dependency\ngraph; that older CI graph and registry-backed packed checks remain separate from the\nnew coordinated candidate. At source preparation (`2026-09-29T13:05:16.240058+00:00`),\nthe original accepted-main CI audit was still pending; its result is recorded\nseparately from these permanent verification requirements.\n\nThe coordinated candidate checks pin accepted #86, retain its original public\nUTC fixture with only the two installed-package import substitutions, and save\nunique source and installed outputs. They also include the six reviewed\nrenderer-absent furniture groups in a separate fresh consumer. The isolated\npreparation passed those groups with copied preview tarballs, including a real\nwrapped date, exact authored whitespace, local overrides and current edited or\ncleared text; it used estimated composition with no renderer or font provider.\nThe acceptance protocol requires 41 sequential stages on the combined accepted\ngraph and an independent whole-byte comparison of its eight furniture PPTX\noutputs against frozen core154 Linux files. Resulting PNGs require separate\nvisual review; package byte equality does not establish raster equivalence.\n\n[FF-11](programs/font-fidelity-everywhere/burndown.md) is **in-progress**, not done.\nFont availability/substitution, LANG/locale, broader OS/runtime/ICU and complete\nexport determinism still need their own evidence. Default behavior after a host\nTZ mutation is excluded from the bounded control. FF-27 remains in review;\nstatic wrapped dates, native refresh/save/reopen, physical fonts, renderer's\n0.1 reference-pixel compatibility limit and release dependency floors remain\nseparate gates. No package or production site changed.\n\n## Release holds\n\nThe retained dependency-floor finding from the earlier PPTX #83 checkpoint remains:\n\nCurrent unreleased PPTX source passes its full portable suite with accepted\ncore `061499d53aabefb266dd4f7f5c5bd85f6d24c425` and renderer\n`6c7d7818e40d0f9c519e4b34f7a24e9150c1787f`. The retained full-suite run with\nregistry core 0.11.0/render 0.9.0 fails `test/furniture-fields.mjs:38`:\na requested `{current} / {total}` yields only the live slide number, without\n` / 3`. Published core 0.11.0 does not expand the new format; accepted core\ncontains that later FF-27 implementation. This is an unreleased capability versus\ndependency-floor mismatch, not evidence that published PPTX 0.9.1 fails its own\nreleased suite. The current renderer, PPTX and editor still declare core\n`^0.11.0`; PPTX/editor also allow the older renderer `^0.9.0`, which lacks the\naccepted explicit host-date forwarding. The coordinated packer rewrites ranges\nto preview versions, so its passing set does not establish those advertised\nminimum versions. Final release tarballs must be tested with their manifests\nunchanged and declared minima forced explicitly, including the supported\nrenderer-absent exporter path. The CLI bundles core and its skills and must be\nrebuilt from the approved source; installing a newer standalone core cannot\nupgrade the existing CLI's embedded implementation.\n\nBefore publishing any package in the new contract:\n\n1. Select the complete candidate set and compatible minimum dependency versions;\n verify fresh isolated tarball installs at those floors. Preserve the accepted\n formatted-field, provenance, source-preservation and browser controls.\n2. Finish the required cross-OS CI and visual review on that exact set, plus the\n separate native field refresh/save/reopen and physical-font gates for the\n release scope. Shared host-date forwarding now has installed integration\n evidence; it is not a missing renderer implementation.\n3. Only after candidate acceptance, publish in dependency order, verify each exact\n registry artifact and supported floor, then verify the complete registry set\n before downstream consumers or production change. Historical registry fixtures\n and older CI sibling pins remain separately identified evidence.\n\nNo target versions, lowered tolerances or release exception are selected here.\nAll accepted source fixes above remain unpublished.\n\n## Remaining PRs and ownership\n\nCore #128 has a reviewed provenance correction and a main integration at\n`e46c278e5dcbe2e2385f7a30c1b9d60aa62d13d4`. All three original workflows passed,\nbut the actual external gallery comparison was skipped because its checkout\ntoken is absent. A separate local comparison passed against clean gallery\n`58aa122690489a9206e1b583e5d9eea5e8cfd84e`. Keep the gallery #46 publisher-first\nhold and the actual external-catalog comparison contract. The comparison may\nrun locally under the owner's policy, but the skipped CI step is not green and\nlocal source parity does not establish that the required catalog is published.\n\nThe captured Data-Advantage Actions denials remain zero-step account/billing\nfailures. The owner's later local-test merge authorization is recorded above;\nrestoring credit is no longer a prerequisite for these reviewed PR merges.\nNo unchanged failed run was retried, and no denial is represented as a test pass.\n\nPPTX #77 remains a substantive theme draft: accepted main does not contain its\nchanges, a read-only merge preview found 17 conflict regions, and per-reference\nacceptance remains unfinished. Its old CI pass does not certify current-main\nintegration. Broader master/theme writing overlaps deferred scope; do not merge\nit as routine cleanup. Keep the five geometry drafts coordinated: core #94,\nrenderer #27, PPTX #42, editor #25 and site #40. They remain substantive. Four\nrepositories conflict with current main; the clean site diff does not make it\nindependently ready. Choosing the old draft side of the core conflicts would\ndiscard accepted image-safe areas, explicit heading alignment and\nreplaced-picture-slot removal. Historical\ngreen checks never tested all five draft heads together. The site's default\ncanvas also needs an exact-source Escape/Undo/Redo control: source review shows\nthat an edited escaped token can be reconstructed with different JSON spelling.\nThat finding is source-derived, not a new browser observation. Never merge only\nthe site half or substitute the old geometry golden for current visual review.\n\n[App #58](https://github.com/Data-Advantage/pptx-dev/pull/58) is merged as\n`4613656add2ed810eb70a7dc3a8a995dd6618255`, at the exact locally reviewed tree.\nIts history guard binds preset undo to exact source, format and document\ngeneration, commits pending preview edits, and invalidates on accepted local,\nhandoff, thread, agent or account replacements. Pending, cancelled, null and\nfailed responses do not invalidate the group. Its earlier and final zero-step\nCI denials remain preserved; the owner explicitly accepted reviewed local\nresults for this merge. Signed-in UI, deferred source effects, broader stale\nresponse ordering and production acceptance remain separate. The September 21\ncanonical 34/39 and Windows 38/39 failures are not erased by this narrower pass.\n\nThe Windows supervisor retains sole desktop Office control and owns its native\nbranches and evidence. Core [#148](https://github.com/OpenPresentation/opf/pull/148),\naccepted as `0e81a407f57e5106ad607a9617c571d86b5428da`, retains the Calibri control.\nThe subsequent explicit-slot control in accepted\n[#149](https://github.com/OpenPresentation/opf/pull/149), commit\n`b5a88a58147bced0ff8b4e94f631d207bd48e122`, still observes an empty name and Aptos\nin the native font collection after filling the four empty theme slots with\nCalibri. Empty slots are unnecessary for that observation; its cause remains\nopen. Reported names and an orderly native lifecycle do not prove physical glyph\nidentity, an allowlist or embedding success. The later accepted [query-order evidence](https://github.com/OpenPresentation/opf/pull/157)\nretains the same collection before and after bounded content reads; it establishes\nstability over that interval, not the cause. This task did not repeat Office testing.\nThe latest accepted parity measurement in core #147 is 5/900 perfect, zero near\nand 895 mismatches on its documented graph; it is not an overall completion\npercentage. Physical fonts, embedding and native wrapping remain unresolved.\nPreserve the renderer 0.1px and native tab 0.02pt gates. Native `p:hf` stays roadmap\nwork, and the Windows owner retains all desktop Office decisions.\n"
128
+ },
93
129
  {
94
130
  "slug": "handoff-windows-native-2026-09-10",
95
131
  "file": "docs/handoff-windows-native-2026-09-10.md",
96
132
  "title": "Windows native testing checkpoint",
97
133
  "markdown": "# Windows native testing checkpoint\n\nRuntime update (September 10): use **Node 24 only** for new development and verification; see [migration instructions](migrations/node24.md). Historical Node 20/24 results and commands below describe prior checkpoints. Keep distinct browser/OS/native gates and the existing Office recovery prerequisite.\n\nThe [Windows evidence bundle](evidence/windows-native-2026-09-10/README.md) records completed accepted-text native testing on the immutable source baselines in its `sources.json`. Both Node 20.20.2 and 24.20.0 passed all 24 separate bounded cases: 144 editable lines, 72 original/saved/edited imports, 24 unchanged save/reopen PNG pairs and 96 original-text ink masks per runtime. The 0.1-reference-pixel containment rule is unchanged. All four owned temporary Carlito registrations were removed after every case, including separate dummy-worker failure/timeout controls.\n\n[PPTX #17](https://github.com/OpenPresentation/opf-pptx/pull/17), retaining the owned worker handle for Windows PowerShell exit codes, and [PPTX #18](https://github.com/OpenPresentation/opf-pptx/pull/18), adding bounded text workers and parent-owned font cleanup, were reviewed and merged by the Mac owner. [PPTX #19](https://github.com/OpenPresentation/opf-pptx/pull/19) contains the separately bounded metric suite. Native results and exact verifier snapshots are in the evidence bundle. Product runtime and release refs are unchanged.\n\nThe complete chart matrix now passes: eight separately selected workbook edits on each Node runtime, 384 original/saved/edited slide imports, 384 independent chart-cache comparisons and 128 identical original/reopened PNG pairs. Each actual selected edit changes its raster and preserves the other seven. [PPTX #21](https://github.com/OpenPresentation/opf-pptx/pull/21) rebinds the existing source range after cell edits and requires exact live edited series before closing the workbook. A pie created entirely by PowerPoint reproduces null category getters after reopening; these are explicitly unavailable observations, while exact live categories and independent persisted cache data remain required. Initial stale-cache, getter, parser and control-save failures remain raw. The earlier `chart.dll` crash's precise trigger remains unknown and its broader probe was not repeated.\n\nThe user reviewed the AutoRecovered presentations and explicitly resumed after an earlier Escape. Preserve those pre-existing presentations. Never kill PowerPoint or Excel, call `Application.Quit`, discard user work or automatically retry a blocked Office call. The bounded helper may close only verified fixtures it opened; helper termination does not establish Office cleanup. Temporary fonts belong to the surviving parent and must be removed in `finally`.\n\nBoth Node runtimes now completed six separate bounded metric runs / 48 slides each. All 144 original/saved/edited imports per runtime pass, while eight tab errors above 0.02 points (maximum 0.0673828125) and one portrait/right native ink overflow at x=497 beyond 496.8 reproduce exactly. Character bounds, inter-part collisions and isolated-mask coverage pass. Original and saved DrawingML retain the precise tab coordinates; the native shaping/observation cause remains under investigation. The current shared metric layout uses advance positions without the general text model's outline clearance.\n\nBoth runtimes now pass the bounded table suite (six actual cell edits, 72 native cells, 978 character colors and 54 slide imports including two further export/import cycles) and code suite (24 imports, actual body/filename edits, zero tab/character-bound outliers). Stronger quote imports exposed first-body-line promotion to subtitle on all six portrait slides; 18 original/saved/edited counterexamples per runtime retain every word but change its role. Native quote bounds and wide imports pass. Earlier footer-only quote checks missed this problem; both verifier generations and raw results remain in the bundle. [PPTX #22](https://github.com/OpenPresentation/opf-pptx/pull/22) contains the bounded content harness and explicitly preserves this failed gate.\n\nThe [coordinated candidate evidence](evidence/windows-native-candidate-2026-09-10/README.md) now records both complete native metric matrices on core `476fdb2f5547e442d8a270047dfc8557f306bb49` and the physical-font graph. The portrait/right Latency ink overflow is cleared under the unchanged containment rule. Six leading-tab outliers remain per runtime (maximum about 0.02267456 pt). The full metric fidelity gate remains failed. The original baseline remains separate, and changing both outline/font inputs does not isolate their individual effects.\n\nThe [fresh quote and native tab evidence](evidence/windows-native-quote-tab-2026-09-10/README.md) verifies merged PPTX #24 on both Node runtimes: 72 slide imports now preserve exact current body/footer roles and title edits. Quotes remain generic text on import. Nine tab/literal pairs created entirely by PowerPoint independently reproduce the native tab offset discrepancy; DrawingML retains precise coordinates. PDF coordinates also differ but have separate rounding. Preserve the 0.02 pt gate and do not add consumer compensation. The successful control, earlier adapter failure, exact verifiers and portable extracted observations remain; private PDFs/font subsets are excluded.\n\nThe [native font/image checkpoint](evidence/windows-native-font-image-2026-09-10/README.md) now records all nine bundled output family/style combinations in actual PowerPoint character properties and PDF glyph font names on both Node runtimes. Each has seven stable save/reopen PNG pairs, 14 identical current-content imports and all nine temporary registrations removed. Initial PostScript/PDF naming mismatch remains raw. Physical file identity for every glyph is not established. OPF editor atomic image replacement, source preservation, guarded failure and exact document/PPTX undo/redo pass on both runtimes.\n\nNative images are blocked. Both preserve/compatible nine-image decks and minimal OPF/direct-PptxGenJS one-PNG controls were refused immediately despite valid XML, archive relationships and source PNG CRCs. A PowerPoint-created picture control timed out after 45 seconds on 2026-09-10 at 20:03:33 UTC; only helper PID 16992 was terminated. The empty owned checkpoint exists, but cleanup and the exact blocked call are unconfirmed because that scratch control did not persist individual stages. No subsequent Office COM calls were made. UI inspection still showed the three pre-existing chart windows without a visible dialog; that does not establish readiness. The user was asked to save wanted presentations, close PowerPoint normally and reopen it before another native image attempt. Preserve all raw attempts and never kill Office or automatically retry a blocked call.\n\nThe Mac owner owns shared product placement and release work. Neither accepted geometry, temporary font registration nor unchanged save/reopen pixels establishes font-file identity, browser/native equivalence or arbitrary round-trip support. No packages or public sites have been published or deployed by this Windows task. The user has authorized creating and merging focused PRs that pass review and checks.\n"
98
134
  },
135
+ {
136
+ "slug": "handoff-windows-native-2026-09-21-wrap-up",
137
+ "file": "docs/handoff-windows-native-2026-09-21-wrap-up.md",
138
+ "title": "Windows native compatibility weekly handoff",
139
+ "markdown": "# Windows native compatibility weekly handoff\n\n## September 29 resumed query-order control (15:07 UTC)\n\n[PPTX88](https://github.com/OpenPresentation/opf-pptx/pull/88) merged as\n`9a7f3c1513c5875b4ac9d5974c04151a4ac26cbe`; its entire tree matches reviewed\nhead `2822107`. The owner authorized local tests in place of Actions during\nthe credit shortage. The final head passed 22 inventory-control groups,\n29 independent audit cases, source build/typecheck/validate/full tests,\nsix browser suites (138 reported checks) and a fresh packed consumer.\nThe initial full suite against published core 0.11.0 failed a known furniture\nexpectation; the coordinated source graph passed without changing tests.\nSource linking completed renderer/PPTX and stopped at the editor's missing\ninstalled dependencies, so no editor validation is claimed. No remote CI pass\nis claimed by this receipt and no workflow or tolerance was changed.\n\nThe [E8 evidence](evidence/windows-native-font-query-order-20260929/README.md)\nrecords one native run using the exact E7 input, SHA-256\n`4e2bab2a4f5a0f09350d2edc2463bcb29302fa7db34a8c621e39fcd5c9a16cd7`.\nThe opt-in comparison retains the initial `Presentation.Fonts` collection and\nreads it once more after the existing bounded content queries, before the\nsame owned close. It completed in about 1,405 ms with 303 stages, one owned\nread-only open/close, unchanged inputs, no temporary fonts and zero audit\nfailures. Both collections and their flags were identical: empty-name entry,\nthen Aptos. All six theme names remained Calibri.\n\nPowerPoint was absent after sleep and launched through supported computer use.\nFresh preflight/postflight showed Home without an open presentation or dialog.\nE8 process 5288 differs from E7 process 30776; executable file/product version\nwas `16.0.20430.20092`. The actual native graph was core `9261eac5`, PPTX\n`9a7f3c1`, renderer `c62b3f9`, editor `d0c95a1`, gallery `f17e9ae5`. The later\nevidence branch also incorporates core156's FF-10 foundation from main, without\nrewriting those historical native inputs or source commits.\n\nFF-05 remains in progress: unchanged means stable only over this query\nsequence/elapsed interval. Root cause, physical glyph identity, font allowlist\nand embedding remain open. The tracker is 19 done, 19 review/in-progress,\n6 todo; the dated parity headline remains 5/900. Next: inspect remaining\nstyle/part references offline and review a minimal fixture change before any\nlater native run. Keep existing failures, the 0.02 pt and 0.1 reference-pixel\ngates, and all Office ownership rules. Do not redo accepted proofs, publish,\ndeploy, retry embedding, implement native `p:hf` or merge deferred geometry.\n\n## September 29 explicit theme-slot control (09:38 UTC)\n\nThe [FF-05 E7 evidence](evidence/windows-native-explicit-slots-20260929/README.md)\nrecords one bounded read-only inventory after changing only four empty theme\nmajor/minor ea/cs attributes in the accepted E6 Calibri control to Calibri.\nThe other 40 ZIP entry contents and all relationships remained unchanged.\nThe audit passed with zero failures: one owned open/close, unchanged inputs,\nno temporary fonts, 1,227 ms under the 45-second helper deadline. PowerPoint\nHome showed no open presentation or dialog before and after the run; the\nrunning executable reported version `16.0.20430.20092`.\n\nAll six theme names reported Calibri, but the initial `Presentation.Fonts`\ncollection still contained an empty-name entry and Aptos. The nonempty inspected\nslide Font2 names remained Calibri; `NameOther` stayed empty. Empty theme ea/cs\nslots are not necessary for this observation, and their removal does not clear\nthe font allowlist. Root cause, physical glyph identity and embedding remain\nunproven. The [brief](programs/font-fidelity-everywhere/aptos-origin-brief.md)\nproposes a separately reviewed second Fonts snapshot after the existing content\nqueries; that harness has not been implemented or run in this evidence.\n\nE6 evidence merged in [core148](https://github.com/OpenPresentation/opf/pull/148)\nas `0e81a407` after independent review and green CI. E7's actual source graph\nwas core `0e81a407`, PPTX `7fca9a2`, renderer `6c7d7818`, editor `d0c95a1`.\nFF-05 remains in progress; tracker counts and the dated parity scoreboard are\nunchanged. No native retry, tolerance change, package publication or deployment.\n\n## September 29 Calibri control (09:01 UTC)\n\nThe [FF-05 E6 evidence](evidence/windows-native-calibri-control-20260929/README.md)\nrecords one successful read-only inventory after changing exactly 17 explicit\nCarlito typeface attributes to Calibri. Other package contents and relationships\nwere preserved, including the four empty theme ea/cs slots. The current audit\npassed with zero failures: no temporary fonts, unchanged input hashes, one owned\nopen/close under the 45-second helper deadline. PowerPoint Home showed no open\npresentation or dialog before and after the run.\n\n`Presentation.Fonts` still reported an empty-name entry and Aptos before other\ncontent was read; nonempty inspected slide font names and theme Latin reported\nCalibri (`NameOther` remained empty). This\nnarrows Carlito-specific explanations but does not identify the exact source,\nprove physical glyph identity or clear the font allowlist/embed gate. FF-05 is\nin progress; next is a separately reviewed style/part isolation control, chosen\noffline before further native work. See the updated\n[brief](programs/font-fidelity-everywhere/aptos-origin-brief.md).\n\nAll five PRs from the preceding review checkpoint merged after independent\nreview and green CI: PPTX79 `0424d561`, PPTX81 `c749c35`, PPTX78 `bf3f78f`,\ncore145 `a85facf`, core147 `061499d`. PPTX80 closed as superseded; its findings\nare reconciled in78 and its branch remains preserved. The fresh source graph\nfor E6 was core `061499d`, PPTX `bf3f78f`, renderer `6c7d7818`, editor `d0c95a1`;\ngallery remained `f17e9ae5`. The final unchanged merged-source parity audit\nretained 5/900 perfect (text 799, fills 791); [core147](https://github.com/OpenPresentation/opf/pull/147)\nrecords that receipt. The committed scoreboard is still the explicitly dated\npre-PPTX78 baseline below. Current tracker: 19 done, 17 review/in-progress,\n8 todo. No publish/deploy or gate change.\n\n## September 29 continuation\n\nResume from fetched `origin/main`. The active goal, acceptance criteria and\nprogress log are now in\n[font-fidelity-everywhere](programs/font-fidelity-everywhere/README.md) and its\n[burndown](programs/font-fidelity-everywhere/burndown.md). The dated sections\nbelow are historical receipts; their pending-work and open-PR statements apply\nonly to their checkpoint date.\n\nInitial fetched source heads at this continuation were core `d3397502`, PPTX `90929546`,\nrenderer `6c7d7818`, editor `214ae695`, and gallery `f17e9ae5`. Use Node\n24.21.0, core pnpm 10.33.2 and locked sibling installs. Existing local branches\nwere preserved, and no old evidence branch was reused as a working base.\n\n**Native progress.** Mixed-table edit/save/reopen already passed on September\n22. The first embed attempt remains a preserved failure before `SaveAs`.\nThe missing FF-04 read-only inventory without temporary font registration has\nnow completed on the canonical unedited fixture in a fresh PowerPoint session.\nIt reports Aptos and an empty-name entry before any edit, as did the historical\nwith-temp and exporter-control runs. The current audits pass; original audit\nfailures remain preserved. The owned source was unchanged, the owned\npresentation closed once, and postflight showed PowerPoint Home without an\nopen presentation or dialog. See the\n[portable inventory evidence](evidence/windows-native-font-inventory-20260929/README.md).\nThis does not prove the font allowlist, physical font identity or embedding.\nFF-05 still needs a discriminating experiment; the\n[Aptos brief](programs/font-fidelity-everywhere/aptos-origin-brief.md) records\nthe narrowed hypotheses and next control.\n\n**PR review.** [PPTX78](https://github.com/OpenPresentation/opf-pptx/pull/78)\nand [PPTX79](https://github.com/OpenPresentation/opf-pptx/pull/79) had green CI\nbut independent review found media privacy/current-content bugs and malformed\nlayout recovery that could crash rendering. Independently reviewed fixes and\nend-to-end regressions pass the current-source package tests. PPTX79 passed\nLinux and Windows CI at `ab0fc1d` and merged as `0424d561`.\n[PPTX81](https://github.com/OpenPresentation/opf-pptx/pull/81) then passed\nLinux and Windows CI and merged as `c749c35`, preserving valid inline layout\nrecords that omit the standalone `$schema` field under PPTX79's validation\ngate. PPTX78 integrates both accepted changes, identity-only late omissions,\nall visible native hyperlink surfaces and exact caption/fallback-link fixes.\nThe latter reconcile [PPTX80](https://github.com/OpenPresentation/opf-pptx/pull/80)'s\nold-base findings; that branch was not merged as-is. The independently reviewed\nPPTX78 head `7cc779129323af6123ef5e194226a545e743f195` passed the full suite,\ntypecheck, validation and six browser suites; exact-head CI\n[36543078806](https://github.com/OpenPresentation/opf-pptx/actions/runs/36543078806)\nis pending at this checkpoint. Its PR records the eventual merge receipt.\nCore132/renderer43 alignment and core133/renderer44 font policy work have\nalready merged.\n\n| Remaining work | State / next gate |\n| --- | --- |\n| FF-04 evidence | [core145](https://github.com/OpenPresentation/opf/pull/145) merged `a85facf`, completing this item with the independently reviewed bundle |\n| FF-05 root cause | Open; Aptos at open does not identify its exact source |\n| PPTX78/79/81 | PPTX79 merged `0424d561`, PPTX81 merged `c749c35`; PPTX78 independently reviewed at `7cc7791`, exact-head CI pending at this checkpoint |\n| PPTX76 and renderer42 | Draft chart coverage; reconcile conflicts and acceptance criteria |\n| PPTX77 | Draft theme-color follow-up; per-reference measurement incomplete |\n| core128 / gallery46 / core144 | Catalog dependency chain; core144 delivered into PR128's `codex/ff-37-gallery-catalog` branch as `f8ca179488ee96c8466303bac06ea2dbf30502a0` at 2026-09-29 08:13:34 UTC, not main; gallery-first/conflict gates remain |\n| gallery40-47 | Open; Actions billing prevents a clean verification result; some also depend on a future release |\n| Old geometry drafts core94/PPTX42/renderer27/editor25 | Deferred; do not merge as part of this work |\n\n**Accepted FF-38 headline: 5/900 perfect, 0 near, 895 mismatch.** The unchanged\nfull audit completed at `2026-09-29T08:23:46.429Z` on core\n`a85facf11d7b99102ca801885c5afaa783d1c800`, renderer\n`6c7d7818e40d0f9c519e4b34f7a24e9150c1787f`, PPTX\n`c749c356b4fb5a5b5dfa77db8f1f7dd3c7daef63` and gallery\n`f17e9ae5869669d5fbac3720f285652d0c37551c`. It improves the September 23\nscoreboard from 4 to 5 without a classification regression; text passes for\n798 and fills for 790. Local raw receipts: `baseline81-results.json` and\n`baseline81-report.md`; the committed [results](programs/font-fidelity-everywhere/gallery-support/parity/parity-results.json)\nand [scoreboard](programs/font-fidelity-everywhere/gallery-support/parity/PARITY.md)\napply the [documented one-slug normalization](programs/font-fidelity-everywhere/gallery-support/README.md#normalized-ids).\nEditor `d0c95a1` was refreshed by a dependency-only merge and was not used by\nparity. The September 23 presence audit remains a distinct historical result.\n\n**Separate candidate measurement.** At `2026-09-29T08:30:22.322Z`, the same\nother heads with PPTX78 `7cc779129323af6123ef5e194226a545e743f195` measured\n5/900 perfect, 0 near and 895 mismatch, text 799 and fills 791, with no\nclassification change from the accepted baseline. Local raw receipts:\n`candidate78-results.json` / `candidate78-report.md`. Pending exact-head CI\nand the eventual [PPTX78 merge receipt](https://github.com/OpenPresentation/opf-pptx/pull/78)\nremain separate gates. This candidate does not replace the accepted headline.\nBoth audits are source/package checks without Office; neither PR-level tests\nnor native inventory validity establishes whole-program native parity.\nFF-29 stays in review, FF-05 stays open, and the tracker remains 19 done,\n16 review/in-progress and 9 todo.\nNo package publication, deployment, shared CI-pin update, native `p:hf`, or\nrelaxation of the 0.02 pt / 0.1 reference-pixel gates is authorized here.\n\nOriginal checkpoint: September 21, 2026. The September 22 continuation below\nrecords harness hardening without replacing the reviewed September 21 native\nevidence. Verification uses Node **24.21.0** and core's declared pnpm\n**10.33.2**. Historical Node20 instructions are superseded. Raw local attempts\nremain preserved; public evidence is linked below.\n\n## September 22 native mixed-edit checkpoint\n\nThe bounded mixed-size table edit/save/reopen proof now has an accepted native\nresult. Evidence is in\n[windows-native-mixed-edit-20260922](evidence/windows-native-mixed-edit-20260922/README.md).\nBoth attempts used fresh output directories, Windows PowerShell 5.1,\nPowerPoint 16.0.20326.20158, source SHA-256\n`f92c5d5565afa1d03fc6df0cdc8d482771d5ebd5a5403f7a888f75e2ad020a51`, the four\ncanonical Carlito faces and registration flags `0`. Each ran once, completed its\nowned Office and font lifecycle, and left PowerPoint with no open presentation or\ndialog. Neither attempt was retried in place. Offline controls and both audits\nran under Node 24.21.0.\n\n- **Attempt 01 failed its audit and is preserved as a failure** (harness from\n PPTX54 merge `86afe6c`). PowerShell coerced the `[string]` stage error parameter\n `$null` to `''`, so all 1,496 stages recorded `\"error\":\"\"`. The harness's\n post-edit reassignment of `Font.Name/Size/Bold/Italic` also wrote explicit\n `i=\"0\"`/`b=\"0\"`, which made the whole-cell `Font.Italic` tri-state read `-2` in\n both the edited and reopened phases. Every run, every probe and the saved XML\n were non-italic.\n- [PPTX55](https://github.com/OpenPresentation/opf-pptx/pull/55) (head\n `0f3a3da6402261610b49774cff5da37716575ece`) makes both stage writers emit JSON\n `null`, adds pure-regression assertions for that, and changes the edit to\n replace text only. Audit gates are unchanged. Linux and Windows CI passed and an\n independent review approved it. It merged as\n `60e33916ddd0cf5ecd88ff61d882eaebab4c988e`; the merged tree is identical to\n the reviewed head that attempt 02 ran.\n- **Attempt 02 passed** the independent audit with 0 failures (worker 4.4 s,\n 5.9 s preflight to supervisor). Content, five-run and seven-probe styles, and whole-cell Carlito/18/bold `-2`/italic `0` all held\n in the original, edited and reopened phases. Outer geometry was\n 43.2/43.2/873.6/118.8 within float precision. Native lines were\n `[0,92) [92,194) [194,245)` in all three phases. The saved and reopened package\n hashes match, and the source was unchanged. The root reviewed the reopened\n full-slide PNG and found no clipping.\n\nThis accepts one finite native edit/save/reopen of this table. The estimated\npreview's `[0,78) [78,172) [172,245)` still differs from native. Preview/native\nparity, general mixed-table layout, font embedding and per-glyph font identity\nremain unaccepted.\n\nFont embedding is next. The Gate E fixture's slide runs and theme major/minor\nfonts are `Aptos Display`/`Aptos`, so the pre-SaveAs allowlist would reject it\nbefore any save. [PPTX56](https://github.com/OpenPresentation/opf-pptx/pull/56)\nadded a reviewed Carlito-only fixture mode, merged as\n`310f873726da976841f61d53c9da04f731007062`; its first embed attempt follows.\n\n## September 22 native font-embed attempt\n\nThe first supervised Carlito-only embed attempt **failed closed before\n`SaveAs`**. It ran once, with harness opf-pptx `main` `310f873` (PPTX56 merged;\ntree identical to reviewed head `6e67741`), and fixture `fixture-carlito-02`\n(source SHA-256\n`f505236ecef4fad838a198449adede1f4afa39d7bac74613626eee2f2a419aeb`). Evidence\nis in\n[windows-native-font-embed-20260922](evidence/windows-native-font-embed-20260922/README.md).\n\nAfter the Gate E edits (`.Text` plus `Font2.Name/Size/Bold/Italic` on the title,\nthe body and four body spans), `Presentation.Fonts` reported `Carlito` and\n`Aptos`, both embeddable `-1`. The allowlist gate blocked on `Aptos`. The owned\npresentation was closed once without saving. No saved copy exists, and the\nattempt was not retried. Font cleanup and input integrity were confirmed. The\noffline OPC audit failed only on the missing saved package.\n\nThe fixture package contains no `Aptos` in any part. Its theme `ea`/`cs` slots\nare empty. Fonts were enumerated only after the edits, and there was no pre-edit\nbaseline, so the origin of Aptos is undetermined. Edit-inserted text taking\nOffice's `ea`/`cs` default and a PowerPoint application default are hypotheses\nonly. Embedding success and per-glyph identity are not claimed.\n\n## September 22 checkpoint\n\nThe continuation hardened native harnesses and their offline controls. No Office call or font API ran,\nand the real mixed-table edit/save/reopen and font-embedding proofs remain\npending. PPTX51 merged as `842214346f49caf61b94eb842c19f6d78fc6d4d7`,\nPPTX52 as `09baff8df42db5dfed7cf8a9450b8ee9f24dab1f`, and PPTX53 as\n`2782479a913a272950db6addb7af5ea69ceb4051`. They add the portable picture\ncomparator, bounded font-embedding harness, and offline mixed-size\nedit/save/reopen harness respectively.\n\n[PPTX54](https://github.com/OpenPresentation/opf-pptx/pull/54) hardens the two\nnew native audits. Its branch is\n`codex/windows-native-harness-hardening-20260922`; reviewed head\n`d84ac917d13db56941e299537848ac861c26adfa` contains implementation commit\n`ce074879` and documentation commit `d84ac917`. Independent review approved\nthe change. Local validation passes 26 mixed controls, 15 font controls, 27\nindependent rejection cases, both Windows PowerShell 5.1 pure regressions,\ntypecheck, validation, the ordinary suite and the packed suite. CI run\n[35757268642](https://github.com/OpenPresentation/opf-pptx/actions/runs/35757268642)\npassed on both Linux and Windows. The PR merged as\n`86afe6c51f8238c3db0450c90443e91378531dc8`. Its post-merge run\n[35758319118](https://github.com/OpenPresentation/opf-pptx/actions/runs/35758319118)\nwas still running when this checkpoint was written. See the merged\n[mixed-edit instructions](https://github.com/OpenPresentation/opf-pptx/blob/86afe6c51f8238c3db0450c90443e91378531dc8/docs/native-mixed-edit.md)\nand [font-embed instructions](https://github.com/OpenPresentation/opf-pptx/blob/86afe6c51f8238c3db0450c90443e91378531dc8/docs/native-font-embed.md)\nfor commands, audit boundaries and immutable output requirements.\n\nCore111 merged as `3c5048522714365a41d9b5b9ba81620affae718b`; post-merge runs\n35662159658 and 35662159864 passed. Renderer30 merged as\n`c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b`; post-merge run 35661504472 passed.\nRepository mains before this documentation update are core `d96c791004a4ec71a7f2af6e06f65c4f13571b9a`,\nPPTX `86afe6c51f8238c3db0450c90443e91378531dc8`, renderer\n`c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b`, and editor\n`5620230086437165bcf2242cbfe2d781bb38ac87`.\n\n| Work | Repository / branch | State at checkpoint |\n| --- | --- | --- |\n| [PPTX54](https://github.com/OpenPresentation/opf-pptx/pull/54) | `opf-pptx` / `codex/windows-native-harness-hardening-20260922` | Merged as `86afe6c51f8238c3db0450c90443e91378531dc8`; reviewed head passed Linux and Windows CI |\n| This handoff | `opf` / `codex/windows-native-handoff-20260922` | Publication branch for this documentation-only update, based on main `d96c791004a4ec71a7f2af6e06f65c4f13571b9a` |\n| [core94](https://github.com/OpenPresentation/opf/pull/94) | `opf` / `cursor/placeholder-geometry-9f55` | Draft, conflicting, deferred geometry work |\n| [PPTX42](https://github.com/OpenPresentation/opf-pptx/pull/42) | `opf-pptx` / `cursor/placeholder-geometry-9f55` | Draft, conflicting, deferred geometry work |\n| [renderer27](https://github.com/OpenPresentation/opf-render/pull/27) | `opf-render` / `cursor/placeholder-geometry-9f55` | Draft, conflicting, deferred geometry work |\n| [editor25](https://github.com/OpenPresentation/opf-editor/pull/25) | `opf-editor` / `cursor/placeholder-geometry-9f55` | Draft, conflicting, deferred geometry work |\n| Native HF | No current open PR | Deferred scope item; no active implementation assigned here |\n\nAt the September 22 inventory, there are no other open PRs across the four repositories. Old `codex/windows-*`\nevidence branches are merged and must not be used as restart bases; squash\nmerges mean their source tips may differ from main. The old editor branch has\nzero unique commits. Two untracked `font-gate-preflight` files in the old local\nresume workspace are historical scratch only.\n\n## Merged resumed work\n\nAll ten PRs below are merged and passed their required CI at the recorded heads.\n\n| PR | Change | Merge commit |\n| --- | --- | --- |\n| [PPTX46](https://github.com/OpenPresentation/opf-pptx/pull/46) | Bounded minimal picture worker | `647159ac886a3e472c4d5f028aa19aaea3257b60` |\n| [core103](https://github.com/OpenPresentation/opf/pull/103) | Minimal native picture evidence | `009ba028e74c512a80eadd9b4c623d01cd43402c` |\n| [PPTX47](https://github.com/OpenPresentation/opf-pptx/pull/47) | Picture/furniture editing controls | `8c7908e7eb02cb4dda52d2fac951c919c60841a5` |\n| [core105](https://github.com/OpenPresentation/opf/pull/105) | Picture/furniture edits and notes ordering | `84e914710520a7b0e777fce30e5758ee64a64924` |\n| [PPTX48](https://github.com/OpenPresentation/opf-pptx/pull/48) | Plain native tab and notes controls | `897b89624b57def84b903a5d64061c4f670640bd` |\n| [PPTX49](https://github.com/OpenPresentation/opf-pptx/pull/49) | Bounded Carlito edits and registry fixture | `9e8a38198519b4d4a18d163928833f2509550687` |\n| [core106](https://github.com/OpenPresentation/opf/pull/106) | Native tab/font evidence | `3847f712ccb2379952bcc8ab7c9fdbaedfd0a4ce` |\n| [core108](https://github.com/OpenPresentation/opf/pull/108) | Finite native tab-coordinate analysis | `9b277e140863389ae06c681a529fac32df8c912c` |\n| [core109](https://github.com/OpenPresentation/opf/pull/109) | Read-only native mixed-table evidence | `b2711549eba48a52f036afd02ee52b761fa0a5f9` |\n| [core110](https://github.com/OpenPresentation/opf/pull/110) | Preserve rich tabs as layout controls | `4f7a4bd494f1a873319eff897423d301d1cfc9d6` |\n\n## Final wrapup deliveries\n\n[Renderer30](https://github.com/OpenPresentation/opf-render/pull/30), merged as\n`c8d7d5ca1f67a7b39f70c7c4bd14577a865b175b`, preserves core tab advances and exact\nsource spans while keeping ordinary unmeasured rich runs in naturally shaped\nchunks. Root and independent review, typecheck, validation, the full renderer\nsuite (805 golden slides / 126 decks), and focused measured/estimated browser\nchecks pass on Node24.21.0 and Edge153. The CI core checkout is pinned to merged\ncore110. Its [first CI run](https://github.com/OpenPresentation/opf-render/actions/runs/35660404111)\npassed rendering/browser checks but failed later because the older PPTX checkout\nlacked `test/color-ref-export.mjs`. The final CI-only commit aligns PPTX/editor\nwith core110's passing coordinated checkpoints, respectively\n`fcc006a6887c549a96a3bc8bbdb957cc54fe67dd` and\n`476191e28e6f5f5ec32146aeb416f5286b4d0570`, without skipping checks or changing\nrendering code. The release owner received the exact coordination changes, and\npost-merge run 35661504472 passed. The initial failed run remains preserved.\n\nThe [native font inventory bundle](evidence/windows-native-font-inventory-20260921/README.md)\nand this handoff are published through [core111](https://github.com/OpenPresentation/opf/pull/111),\nmerged as `3c5048522714365a41d9b5b9ba81620affae718b`; both post-merge\nruns passed.\nThey preserve the last observation and remaining work. Its 37 files\npass the offline verifier; manifest SHA-256 is\n`18ca9125a22bb65c79bc1c7a788a3bf5a2ab2ff41248c81d158b1833a1f8085d`.\nThe native font allowlist remains failed. The original parent failure is intact,\nand the corrected lifecycle interpretation is labeled as an offline audit.\n\nThese concluded the September 21 native work for the owner-requested weekly\npause. The September 22 continuation above records subsequent harness work;\nthe next Office proofs remain pending. Merging source changes does not publish\nan npm train or deploy a site.\n\nThe renderer's first full-suite invocation could not spawn a child process\nunder the sandbox (`EPERM` at 0ms). The same suite passed with process creation\npermitted; no test budget or golden changed. Chromium's DOM preserves the tab,\nwhile Selection serializes it as a space. Browser checks retain the separate\n0.1 reference-pixel gate. The reviewed full-slide PNG contains all three lines\nwithout visible clipping; this is not native parity.\n\n## Frozen results and finite limits\n\n- [Plain native tabs](evidence/windows-native-tabs-fonts-20260921/README.md)\n retain content and save/reopen positions, but the unchanged **0.02pt** gate\n fails: target error `0.022655487060546875pt` and tab/literal difference\n `0.022678375244140625pt`. Do not add offsets or relax the tolerance.\n- [Picture/furniture controls](evidence/windows-native-edits-20260921/README.md)\n pass finite edits, current-content provenance and cleanup. Crop import reports\n its limitation; Change Picture changes geometry; longer edited furniture\n clips. These results do not establish general reflow or native `p:hf` support.\n- Production notes ordering opens/saves/reopens. Reordered diagnostics are\n refused at open (`0x80070570`); preserve production ordering.\n- The [mixed-table observation](evidence/windows-native-mixed-table-20260921/README.md)\n retains all 245 characters, the literal tab and five rich runs. Native\n soft-line boundaries are 92/194 versus the old estimated preview's 78/172,\n and native default tab spacing is 72pt. This is not edit/save/reopen or\n browser/native fidelity acceptance.\n- The four-face Carlito native edit control passes text/style persistence,\n bounds and raster stability. The later read-only inventory reports **Carlito\n plus Aptos**, so the permitted-font allowlist fails. No embedding, physical\n face identification or per-glyph identity claim follows.\n- A local offline reimport diagnosis finds that the plain-shape importer drops\n rich styling before heading inference. Exact text is retained. No production\n importer repair or new native-edit claim was made from that diagnosis.\n\nThe last owned Office attempt confirms one presentation close and four font\nremovals. The subsequent host inspection showed an empty workspace. No owned\npresentation, helper or temporary font registration is outstanding. This dated\nobservation does not prove the host is ready for a future session.\n\n## Evidence and release boundaries\n\nSource-linked integration was tested with core head\n`fc3c36e929492d22b5f5af944a6ca3a12a9784d9` (merged as core110) and the renderer\ncompanion in PR30. Its tests do not rewrite or revalidate the frozen registry\nconsumer: core0.11.0, CLI/renderer0.9.0, PPTX0.9.1 and editor0.8.0, with lock SHA\n`4868f13254e3e681566164da8427d3716ceaa025435e4606e5e15fc8a87bc305`.\nKeep source, packed-candidate and registry claims separate. No new package\nversion was published by this native task.\n\nThe future inventory worker02 is preserved in the bundle at SHA\n`fd279c971ba9447770931c8972cc5a4d2b21df9d77c40d90135e571db7ce85ed`.\nIts executed checks cover JSON-array parsing and negative controls only; it has\nnever run Office. The raw parent parser failure remains available alongside\nthe offline correction. The v2 audit only changes duration serialization from\n1.3689453 to 1.368945 seconds; it does not change a result or gate. Superseded\ngenerator/report hashes are recorded in `generation-corrected.json`; their\noriginal bytes remain preserved locally outside the compact bundle.\n\nRelease management, package publication, sites and public-app issue88 belong\nto the separate main task. Geometry/HF drafts core92/94, renderer27, editor25,\nPPTX41/42 and site40 were not changed. Archived shaping stays archived. The\noverall compatibility goal remains incomplete.\n\n## Next-week restart prompt\n\nResume Windows native compatibility from fresh `origin/main` worktrees. Do not\nresume an old evidence branch, and do not treat squash-source differences or\nthe two historical `font-gate-preflight` scratch files as pending work. Refresh\nrepository and PR state first. Confirm PPTX54's final CI and merge receipt, then\nrun its offline controls from the reviewed merged source before any native use.\nUse Node24, Windows PowerShell 5.1 and each repository's declared package\nmanager. Preserve dirty work, original failures and the separate registry\nconsumer.\n\nThe bounded mixed-table edit/save/reopen proof passed on September 22 (see the\nnative mixed-edit checkpoint above). The first Carlito-only font-embed attempt\nfailed closed on `Aptos` before `SaveAs` (see the font-embed attempt above).\nThe next step is a Fonts diagnostic, before any second embed attempt:\n\n- a read-only `Presentation.Fonts` inventory of the unedited fixture;\n- a pre-edit baseline enumeration in the harness.\n\nUse fresh output directories and the reviewed mixed source SHA-256\n`f92c5d5565afa1d03fc6df0cdc8d482771d5ebd5a5403f7a888f75e2ad020a51`.\nBoth harnesses require the four canonical Carlito face hashes and numeric\nregistration flags `0`. Mixed-edit uses `SaveAs(..., 24, 0)` and requires content,\nstyle, outer geometry within 0.02pt, and matching edited/reopened native line\nintervals. The known preview mismatch is not a failure. Font-embed requests\n`SaveAs(..., 24, -1)` on its owned presentation only.\n\nThe embedding worker must check its permitted-font inventory before\n`SaveAs`; unexpected Aptos blocks `SaveAs` and must be reported as the failed\ngate. This is a runtime harness rule, not a new preflight approval requirement.\nDo not infer preview/native parity, general export below 0.02pt, embedded-font\nsuccess, or physical/per-glyph font identity from preparation or font-family\nproperties.\n\nInspect the current host before any Office call. Root alone owns Office and\ntemporary font registrations, one reviewed bounded worker at a time, with a\n45-second default and 60-second maximum. A timeout may terminate only the owned\nhelper. Never kill Office, call `Application.Quit`, close unrelated\npresentations, change Office security, or retry automatically. Keep the 0.02pt\nnative and 0.1px browser gates unchanged. Leave `p:hf`, the deferred geometry\ndrafts and Native HF untouched.\n\nThe published train remains core0.11.0, CLI/renderer0.9.0, PPTX0.9.1 and\neditor0.8.0. Do not publish packages or deploy sites from this continuation.\nCoordinate any later release work with its owner; public-app acceptance remains\nseparate from the Windows native task.\n"
140
+ },
99
141
  {
100
142
  "slug": "handoff",
101
143
  "file": "docs/handoff.md",
102
144
  "title": "Continue the OPF ecosystem work",
103
- "markdown": "# Continue the OPF ecosystem work\n\nRuntime update (September 10): use **Node 24 only** for new development and verification; see [migration instructions](migrations/node24.md). Historical Node 20/24 results and commands below describe prior checkpoints. Keep distinct browser/OS/native gates and the existing Office recovery prerequisite.\n\nThe coordinated ecosystem PRs were merged on September 8, 2026 UTC. Continue from `main` in these repositories:\n\n| Checkout | Merged PR |\n| --- | --- |\n| opf | https://github.com/OpenPresentation/opf/pull/9 |\n| opf-render | https://github.com/OpenPresentation/opf-render/pull/1 |\n| opf-editor | https://github.com/OpenPresentation/opf-editor/pull/1 |\n| opf-pptx | https://github.com/OpenPresentation/opf-pptx/pull/1 |\n| pptx-gallery | https://github.com/Data-Advantage/pptx-gallery/pull/9 |\n| openpresentation-site | https://github.com/Data-Advantage/openpresentation-site/pull/5 |\n\nClone the six repositories into sibling directories. Use Node.js 24 and pnpm 10.33.2. From the parent directory:\n\n```sh\ngh repo clone OpenPresentation/opf -- --branch main\ngh repo clone OpenPresentation/opf-render -- --branch main\ngh repo clone OpenPresentation/opf-editor -- --branch main\ngh repo clone OpenPresentation/opf-pptx -- --branch main\ngh repo clone Data-Advantage/pptx-gallery -- --branch main\ngh repo clone Data-Advantage/openpresentation-site -- --branch main\n```\n\nInstall dependencies with `pnpm install --frozen-lockfile` in `opf`, `pptx-gallery` and `openpresentation-site`; use `npm ci` in the three library repositories. Then, from `opf`:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm test\npnpm test:skills\npnpm test:ecosystem\npnpm test:layout\npnpm test:lists\npnpm demo:editor\npnpm pack:ecosystem\npnpm test:packed-ecosystem\n```\n\nThe link step builds the sibling libraries against the current core. Re-run it after reinstalling dependencies. Core 0.4.0, renderer/editor 0.1.1 and PPTX/CLI 0.1.0 are now published. The source-link workflow remains useful for development. Generated review artifacts, installed dependencies and local server state are excluded from Git and rebuilt by these commands.\n\nStart the editor with `python3 -m http.server 3102 --directory artifacts/editor` from `opf`. In another terminal start the gallery with `OPF_LOCAL_WORKSPACE=1 pnpm dev --port 3101` from `pptx-gallery`. From `openpresentation-site`, run `OPF_LOCAL_SOURCE=../opf pnpm sync:opf`, then `pnpm dev --port 3103`.\n\nFor public-site documentation built from a remote branch instead of the sibling checkout, use `OPF_REPO_REF=codex/opf-ecosystem-20260907 OPF_FORCE_SYNC=1 pnpm sync:opf`.\n\nProduction builds use `OPF_LOCAL_WORKSPACE=1 pnpm build` in `pptx-gallery` after linking, and `OPF_LOCAL_SOURCE=../opf pnpm build` in `openpresentation-site`. The gallery workspace flag lets Turbopack resolve the sibling package. Normal standalone gallery builds now use registry core 0.4.0. Normal site builds default to the source tag matching the installed core version; stale/local snapshots refresh automatically unless OPF_LOCAL_SOURCE explicitly selects local development.\n\n## Current release checkpoint \u2014 September 8 UTC\n\nPR #8 is incorporated into the pushed PR #9 branch through merge 10ed11c. Both test suites, structural package slimming, named schema definitions, catalog/index fixes, governance and Node 20/24 release gates are retained. All six PRs are merged with merge commits; GitHub also marked PR #8 merged through its preserved ancestry. Both sites deployed automatically from the merged main branches.\n\nAll five planned versions are now published and resolve through ordinary npm installation:\n\n| Package | Version | Source and publication |\n| --- | --- | --- |\n| @openpresentation/opf | 0.4.0 | Tag opf-v0.4.0 at 6180096; workflow 34182120112 with provenance |\n| @openpresentation/opf-render | 0.1.1 | Tag opf-render-v0.1.1 at de53df7; workflow 34184779283 with provenance |\n| @openpresentation/opf-editor | 0.1.1 | Tag opf-editor-v0.1.1 at dbbe1a1; workflow 34184868180 with provenance |\n| @openpresentation/opf-pptx | 0.1.0 | Tag opf-pptx-v0.1.0 at 7a385fc; retried workflow 34182814991 with provenance |\n| @openpresentation/cli | 0.1.0 | Reviewed standalone tarball from core 6180096, authenticated first publication; no provenance on this bootstrap release |\n\nThe user completed npm login and browser 2FA. Trusted publishers now exist for editor/PPTX release.yml and core cli-publish.yml. Initial permissions failures were resolved, and exact tagged jobs reran successfully. The CLI public tarball SHA-1 is 0308493d1d85ce18518b69882085419ec3817d73, matching the reviewed artifact. npm metadata took several minutes to expose the new package; it now installs normally. Do not republish an existing version.\n\n`pnpm test:registry-ecosystem` passed for all five exact versions with no local overrides: model operations, fonts, SVG, editable PPTX, TypeScript, browser bundle, CLI create/validate/version. All 60 CLI command checks also pass using the registry-installed executable. `release-plan.json` pins immutable core/editor harness refs for the published release set so unreleased source tests cannot silently change release verification. `test:registry-libraries` is an additional four-library check, not a replacement for the complete gate. All six registry-installed browser suites pass 176 checks (34 canvas, 19 lists, 46 rich text, 19 layout, 18 blocks, 40 creation). Browser evidence lives in `artifacts/npm/registry-browser-verification.json`.\n\nPublished in 0.1.1: renderer 5472483 adds trace-only rich-line geometry; editor a4be429 adds native continuous rich typing with glyph-aligned caret/pointer selection, mixed-style preservation, draft undo/redo, cancellation, conflict protection and composition lifecycle handling. Source browser suites pass 176 checks (34 canvas, 19 lists, 46 rich text, 19 layout, 18 blocks, 40 creation). Actual keystrokes and a measured pointer hit were verified. Renderer/editor 0.1.1 passed standalone Node 20/24 CI (34184700599 and 34184801283), trusted publication, and clean five-package registry verification. Real OS IME, bidi/complex-script and cross-browser behavior remain open. Normal renderer output is unchanged and all 805 raster checks pass.\n\nThe renderer baseline covers 805 slides in 126 installed-core example decks; missing/changed corpora fail and updates create review candidates without replacing the baseline. All 17 overview sheets were inspected and timeline endpoint clipping was fixed. PptxGenJS remains pinned to 4.0.1; its unused image-size advisory remains unresolved, with model/image embedding tested while parser loading is blocked. Neither baseline nor model checks establish native PowerPoint fidelity.\n\nGallery review fix ee01bc5 passes production build and header geometry checks at 320, 640, 1024, 1279, 1280 and 1440 pixels. Phone/laptop screenshots and mobile search were checked. Site review fix fe638e8 removes duplicate sitemap routes, preserves root-relative guide links, omits catalogs without indexes and repairs code-block colors. Its prebuild regression suite covers 592 unique sitemap URLs, link resolution and missing/empty/legacy catalogs. All five confirmed review threads were resolved before merging.\n\nThe gallery editor and site showcase have now been regenerated from the exact npm set above. All 854 gallery documents validate and render using the installed packages; the schema reference exposes 604 fields. Checked-in manifests record package tarball URLs/integrities, example source refs and SHA-256 asset hashes. Normal production builds pass. Served checks pass for nine gallery documents, 583 site source hashes, six skills, seven guides and all refreshed editor/showcase asset hashes.\n\nReproduce registry assets after installing core build tooling and fetching the immutable harness/example refs in release-plan.json:\n\n```sh\npnpm test:registry-ecosystem\npnpm prepare:gallery:registry\npnpm build:showcase:registry\n```\n\nThe gallery command updates its checked-in editor/reference files. Copy the four files in artifacts/site-showcase to openpresentation-site/public/showcase, then build both sites. Registry checks generate their own browser HTML and font files; prior source-demo artifacts are no longer required. Source preparation removes any old registry manifest so it cannot falsely label a development bundle as published.\n\nFinal reviewed core ece9b90 passed core CI 34185231548 and coordinated CI 34185231533 on Node 20/24. The workflow pins renderer/editor release commits. Main now contains the complete ecosystem implementation and the 0.4.0 changelog correction. The user authorized pushes, PR updates, npm publication, and these six merges with their normal site deployment triggers. Preserve unrelated gallery pnpm-workspace.yaml.\n\n## Current scope and remaining work\n\nThe branch includes shared dynamic layout and pagination, loaded-font measurement and substitutes, rich text/lists, CSV/JSON import, an installable agent CLI, six portable skills, schema-driven properties, copy/import/galleries, canvas resizing/moving/creation/deletion, native PPTX improvements, and site/galleries integration. See [ecosystem quality](plans/ecosystem-quality.md) and [coverage](plans/spec-editor-coverage.md) for evidence and remaining fidelity gaps.\n\nThe broader goal is still active. Real OS IME/cross-browser typing, advanced table/media/preset fidelity, and native PowerPoint raster comparison remain work. Local preview tarballs are not evidence of registry publication.\n\n## Merged checkpoint and local follow-ups\n\n| Repository | Main merge commit |\n| --- | --- |\n| opf | c6323d9 |\n| opf-render | 371c6ce |\n| opf-editor | 33cfebc |\n| opf-pptx | e3afb70 |\n| pptx-gallery | 22f1748 |\n| openpresentation-site | b385495 |\n\nBoth production deployments are ready at www.pptx.gallery and www.openpresentation.org, from the exact merge commits above. Releases were already published before merging; no versions were republished.\n\nPost-merge checks passed: core 34186567364, coordinated packages 34186567415, renderer 34186581381, editor 34186583983, PPTX 34186586280 and gallery 34186589351. Live verification passed for 583 source-file hashes, six downloadable skills, seven guides, schema/text endpoints and nine schema-valid gallery documents. All seven gallery manifest hashes and three showcase hashes match production. The manifest's opf-spec.json is served at /api/opf-spec.json; the other editor assets are under /opf-editor/. The live editor renders six starter slides and opens its complete property controls.\n\nThe following local follow-ups remain outside this merged checkpoint. The PPTX table and image branches are now combined in the integration checkpoint below:\n\n- PPTX table fidelity: 7453c33 (c549049 fitting plus ba0ad8a import fixes, reconciled with main) on codex/table-export-fidelity-20260908 in /private/tmp/opf-table-export/opf-pptx. Native editable cells use shared loaded-font fitting, preserve nested minimum font sizes, fill uneven rows, and match the SVG border theme slot. Node 20/24 tests compare 168 cells, including 24 shrinking cases. Quick Look and Keynote can open the exports, but wrapping differs. Keynote's round-trip changes the specimen's 11.25/6.75-point text to 11/6 points; this is viewer-specific evidence, not PowerPoint verification.\n- Site snapshot source links: 2858456 (d5642af reconciled with main) on codex/site-snapshot-links-20260908 in /private/tmp/opf-site-snapshot/openpresentation-site. View-source links serve the exact documentation snapshot bytes instead of GitHub main. Regression tests, production build, 583 raw-file hashes, six skill downloads, seven guides and representative source links pass locally.\n\nNext: review the combined PPTX integration and separate site source-link change, then continue native PowerPoint comparison, real OS typing, and the remaining table/media/preset roadmap. The local commits are not published releases.\n\nThe table follow-up also preserves headerless first rows and empty rows on import using native firstRow flags. Node 20/24 tests and a clean local-tarball consumer pass. Keynote 14.4 recognizes zero headers/three rows versus one header/four rows in a two-slide specimen, with blank rows retained. Native PowerPoint remains untested. Local PR descriptions are ready; approval to open the two new PRs is pending.\n\n## Local image-fidelity follow-up\n\nCommit 0007ff1 on codex/image-fit-fidelity-20260908 in /private/tmp/opf-image-fit/opf-pptx is separate from the table branch. It preserves native picture proportions with fit/crop, honors slide overrides, and maps all eight JPEG EXIF orientations to native rotation/mirroring without recompressing pixels. PNG/JPEG/GIF/WebP dimensions come from the actual embedded bytes. Unsupported/unreadable dimensions produce a path-specific error. Node 20/24 suites pass with image-size loading blocked; 45 geometry cases, eight orientations in fit/crop, clean local-tarball installation and browser bundling pass. Keynote visually shows correct wide/tall PNG fit/crop and all eight JPEG orientations. Native PowerPoint, animated/vector media, non-JPEG orientation and lossless picture import remain open. The change is committed locally and unpublished.\n\n## Combined PPTX integration checkpoint\n\nLocal merge commit 4a41b2b on codex/pptx-fidelity-20260908 in /private/tmp/opf-pptx-fidelity/opf-pptx combines the table and image branches above. The original branches are preserved. An installed-example audit also exposed duplicate native object IDs on 23 slides; the integration repairs only duplicate IDs and adds an eight-slide mixed table/text/image/chart/list regression. Repeated multi-chart exports also exposed ZIP ordering differences when PptxGenJS counters cross digit widths; sorting after part-name normalization fixes them.\n\nNode 20 and 24 pass the integrated suites, syntax checks and package metadata validation. A new structural corpus gate exports/imports all 126 decks / 805 slides, checking slide XML, unique IDs, finite geometry, table grids and imported slide counts. It explicitly uses fallback fonts and 26 synthetic image substitutions. With original asset requests and fallback fonts, 102 decks export/import; the remaining 24 encounter missing local assets or a truncated PNG fixture. With strict available fonts and original assets, only one deck completes. These are different evidence levels: the complete structural gate is not proof of original-asset, typography or native-viewer fidelity.\n\nThe clean consumer in /private/tmp/opf-pptx-fidelity/consumer passes the complete suite against the installed local tarball plus published core 0.4.0 and renderer 0.1.1, including the structural corpus and a browser bundle.\n\nLocal packed artifact: /private/tmp/opf-pptx-fidelity/packed/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 ea3215e9cee82366e131e6b3d3115cd5e19a0b8e. Version 0.1.0 is unchanged for this unpublished development artifact; it must not overwrite the existing npm release. No follow-up branch has been pushed, no new PR opened, and no new package published. The earlier request to open follow-up PRs remains pending; the prepared PPTX description now covers this combined implementation.\n\n## Image media-type and example corrections\n\nPPTX integration now advances to 87f6616. Native raster file extensions and per-part content types are derived from actual embedded PNG/JPEG/GIF/WebP bytes, so transformed host assets and incorrect MIME/path hints cannot label JPEG pixels as PNG. Import detects these formats from bytes as well. Thirty-two cases cover byte results, typed/untyped result objects, data URIs, local paths and host paths/URIs, plus mislabeled older native files. Node 20/24 complete suites and package checks pass. The clean consumer at /private/tmp/opf-pptx-fidelity/consumer-87f6616 passes all tests, the 805-slide structural corpus and browser bundling with published core 0.4.0 / renderer 0.1.1. New local tarball: /private/tmp/opf-pptx-fidelity/packed-87f6616/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 15ce44e1ee1faed81aed711a3e3795a0974d7145. Earlier tarballs remain historical artifacts, and no new version is published.\n\nKeynote 14.4 directly opened the four-format specimen at artifacts/media-types/native-media-types.pptx in the integration worktree. PNG, JPEG and GIF show the expected quadrants and round circle. WebP imports as an empty rectangle, confirmed in both the slide overview and selected slide. That check established the need for compatible PNG fallback; the next checkpoint below implements and verifies it. Correct ZIP metadata alone was insufficient. No native PowerPoint installation is available. The generated specimen was closed without saving.\n\nCore local commit ef25735 replaces the eight-byte PNG signature in examples/technical/asset-source-forms.opf.json with the complete project-authored square PNG from the PPTX test fixtures. All 126 examples validate. The corrected example exports/imports four slides with its exact PNG bytes retained, and its SVG/PNG image slide was visually inspected using explicit fallback fonts. Core/CLI build verification includes checking the generated examples against this source. Other illustrative file/remote references remain host-supplied, not bundled. The correction is unreleased and requires a future core package/site snapshot update.\n\n## Compatible WebP checkpoint\n\nPPTX integration advances to local commit 8197854 on codex/pptx-fidelity-20260908. Default imageFormat: \"compatible\" converts WebP to static PNG after resolving the original asset once; fit/crop uses the decoded PNG dimensions. Pixel comparisons cover alpha, EXIF orientation/mirroring and the first animation frame. imageFormat: \"preserve\" retains unchanged WebP embedding. Conversion errors carry the OPF path, and compatible conversion has a 40-megapixel limit. Original source documents/bytes are unchanged; the exported picture contains PNG pixels, not original WebP metadata or animation.\n\nNode uses lazy Sharp 0.35.4 decoding, raising the package minimum to Node 20.9.0. Browser conditional imports select Blob/image/canvas decoding and exclude Sharp/Node code. Platform dependencies are present in the lockfile; npm's optional native dependencies must be installed normally. The audit still reports only the existing image-size/PptxGenJS advisories. esbuild 0.28.2 is a development-only dependency used to enforce the browser boundary.\n\nNode 20.20.2 and 24.20.0 complete suites, syntax and metadata checks pass. Thirty-six WebP cases match independent Pillow-decoded first-frame RGBA hashes; decoder-unavailable and malformed/oversize cases produce path-specific errors. Existing preservation tests explicitly select preserve mode. The 126-deck / 805-slide structural corpus remains green with its previously documented substitutions. Thirteen browser pixel/geometry/input checks pass, including alpha, EXIF, animation's first frame and DOM canvas output. npm test builds the browser verification page and asserts no native modules enter its bundle; actual browser execution remains a separate UI check.\n\nKeynote 14.4 now displays all six converted WebP specimens in /private/tmp/opf-pptx-fidelity/opf-pptx/artifacts/webp-fallback/compatible-webp.pptx. The earlier empty rectangles are gone; expected quadrants, circles, transparency and orientation are visible. Arial labels avoid the previous missing-font notice. The generated document was closed without saving. Microsoft PowerPoint and other browsers remain unverified, and SVG/vector assets and full animation playback remain separate work.\n\nLocal tarball /private/tmp/opf-pptx-fidelity/packed-8197854/openpresentation-opf-pptx-0.1.0.tgz has SHA-1 6c249cf52d0624c9408bd8abb4e544d444b20c3a. Clean consumer /private/tmp/opf-pptx-fidelity/consumer-8197854 installs published core 0.4.0 / renderer 0.1.1 and this tarball, and passes the complete model/image/corpus suite and browser bundle checks. The packed browser implementation also passes all 13 browser checks. Both decoder files and conditional package imports are packaged. This is unpublished development version 0.1.0; do not overwrite the existing registry release. No follow-up PRs, pushes or publications have been made.\n\n## Renderer WebP raster checkpoint\n\nLocal renderer commit 18c79e4 on codex/renderer-webp-20260908 in /private/tmp/opf-renderer-webp/opf-render fixes WebP images disappearing from PNG/PDF output. The new Node-only raster preparation decodes embedded WebP data URIs to static PNG; it leaves the source SVG/OPF unchanged and adds no external URL/path resolution. href/xlink:href, base64/percent encoding, XML character references, alpha, EXIF and the first animation frame are covered. Malformed input reports its data-opf-path when present or svg.images.N. Input format is checked before decoding and the 40-megapixel Sharp limit applies.\n\nNode 20.20.2 and 24.20.0 full suites, syntax/package checks and all 805 existing golden rasters pass; no baseline changed. Twenty-three focused cases inspect raster pixels, fit/crop, actual PDF image streams and PDF alpha masks. The historical renderer demonstrably fails the first independent pixel reference. Fixtures regenerate byte-for-byte with Pillow 12.3.0; the six-image overview PNG was visually inspected. PNG/PDF conversion uses lazy Sharp 0.35.4 and now requires Node 20.9+. The renderer audit reports zero advisories at this checkpoint. Browser SVG exports exclude the native raster adapter.\n\nThe local renderer tarball /private/tmp/opf-renderer-webp/packed/openpresentation-opf-render-0.1.1.tgz has SHA-1 d19e216a4e8ec0762ed9e912b0708002ec43be64. /private/tmp/opf-renderer-webp/consumer installs it with local PPTX 8197854 and published core 0.4.0, editor 0.1.1 and CLI 0.1.0. Both packages' focused/model/corpus checks pass, browser bundling excludes native decoders, editor import succeeds and CLI reports its expected versions. This is a mixed local/registry development consumer, not evidence of a new registry release.\n\nThe renderer branch and all earlier follow-ups remain local/unpublished. Prepared descriptions now cover four potential follow-up PRs: PPTX fidelity, renderer WebP raster output, the core example correction/handoff, and site snapshot source links. Existing examples/font substitutions and native Microsoft PowerPoint coverage remain separate limits. The JPEG PNG/PDF gap is addressed by the next checkpoint; other media/layout/editor requirements remain open.\n\n\n## Renderer JPEG orientation checkpoint\n\nLocal renderer commit 87663fb extends codex/renderer-webp-20260908 in /private/tmp/opf-renderer-webp/opf-render to honor JPEG EXIF orientations 2\u20138 in PNG/PDF output. It uses the existing lazy Sharp decoder and private rasterization copy. JPEGs with orientation 1 or no orientation retain their exact SVG attribute spelling and compressed bytes. Source OPF/SVG and browser SVG exports remain unchanged. Malformed JPEGs report the image path.\n\nNode 20.20.2 and 24.20.0 verification passes. The final Node 20 suite, syntax and metadata checks are recorded in artifacts/jpeg/node20-verification.log in the renderer worktree; all 805 golden rasters remain unchanged. Twenty-four focused cases cover all eight orientations, fit/crop, independent Pillow pixels (maximum channel difference 2), actual PDF RGB image streams, deterministic PNG output and errors. The historical renderer fails orientation 2 with a maximum channel error of 255. All 16 fixture JPEG/PNG files regenerate byte-for-byte using Pillow 12.3.0. The eight-image PNG overview was visually inspected.\n\nSixteen browser OPF-SVG orientation/fit/crop cases passed before the Mac locked, with an incorrect-orientation control. Maximum mean channel error was 0.6156662326388889, maximum fraction of channels differing by more than 10 was 1.2241753472222223%, and maximum individual channel difference was 49. The incorrect-orientation control had mean error 56.44899848090278. These are geometry/orientation checks with aggregate error bounds, not pixel-identical browser output. The checked-in harness is test/jpeg-browser.js; npm test builds it and asserts native raster code is absent. No additional browser run was performed after the Mac locked.\n\nPacked artifact: /private/tmp/opf-renderer-webp/packed-jpeg/openpresentation-opf-render-0.1.1.tgz, SHA-1 c239043a3230a9d4fb55f8213abde1d1ef12a7e8. A fresh /private/tmp/opf-renderer-webp/consumer-jpeg installs this tarball, local PPTX 8197854, and published core 0.4.0/editor 0.1.1/CLI 0.1.0. All 23 WebP and 24 JPEG focused cases pass against the installed renderer. Editable PPTX, SVG/PNG, editor import and the combined browser dependency boundary pass. The development artifact retains version 0.1.1; it is not a registry publication and must not overwrite that released version.\n\nGitHub was rechecked: all six coordinated PRs remain merged. The four follow-ups remain local, with the earlier request to push/open their draft PRs still pending. The renderer description now covers both WebP and JPEG raster fixes. Native Microsoft PowerPoint, additional browsers and remaining media/editor fidelity remain open.\n\n\n## Native background fill checkpoint\n\nLocal PPTX commit 4c06cf7 advances codex/pptx-fidelity-20260908 in /private/tmp/opf-pptx-fidelity/opf-pptx. Fixed solid and linear-gradient backgrounds now serialize as editable native p:bg fills rather than flattening gradients to a solid fallback. Solid opacity, hex alpha, gradient stop alpha/positions, deck defaults and slide overrides are retained. An inheritance test also exposed and fixed ignored inline theme overrides: theme objects now resolve their base record and then apply the inline properties.\n\nSVG's object-bounding-box gradient and DrawingML's unscaled slide-coordinate gradient need both direction and stop-interval conversion. The new background helper performs that conversion and native RGB solid/linear import without hidden OPF metadata. Native integer angles/positions incur rounding. Uniform alpha returns as background opacity; differing stop alpha returns as eight-bit RGBA, which may round. Unsupported native path gradients, transformed/theme stop colors, non-default tile/flip geometry and unrepresentable stop intervals produce unsupported-background-gradient through the new optional fromPptx onDiagnostic callback. Other background/decorative features remain open.\n\nFinal Node 20.20.2 and 24.20.0 full suites, syntax and metadata checks pass (artifacts/backgrounds/node20-final.log and node24-final.log in the PPTX worktree). Coverage includes 38 principal gradient/solid cases and 990 sample comparisons derived independently from serialized SVG endpoints and native physical gradient properties across landscape, portrait and square canvases. Additional cases cover theme inheritance, alpha, native edits, repeated export/import, empty/single/descending stops, malformed colors and unsupported native fills. The historical implementation fails the first editable-gradient assertion. The structural corpus still passes 126 decks / 805 slides using its documented fallback fonts and 26 synthetic image substitutions; that is not native raster parity evidence.\n\nNative visual verification is unresolved. Four Quick Look thumbnails (0, 30, 45 and 90 degrees) all contain the same flat RGB(106,176,222), rather than the intended gradient. The PNG files and corresponding PPTX/SVG references are under artifacts/backgrounds. Keynote UI inspection was attempted but the desktop tool reported the Mac locked; an unlock request is pending. Microsoft PowerPoint is unavailable. The native XML/mathematical checks must not be treated as proof of native appearance. This discrepancy needs investigation in a native viewer before a release decision.\n\nLocal package: /private/tmp/opf-pptx-fidelity/packed-backgrounds/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 ceacb66d28703b3de866db6847084df59b0dc893. Fresh consumer /private/tmp/opf-pptx-fidelity/consumer-backgrounds installs it with local renderer 87663fb plus published core 0.4.0, editor 0.1.1 and CLI 0.1.0. Installed gradient checks, all 47 JPEG/WebP raster cases, editor import and the combined browser dependency boundary pass. The package still has development version 0.1.0 and must not overwrite the existing registry release. No follow-up pushes, PRs or publications occurred.\n\n\n## JPEG picture import and dimension precision checkpoint\n\nPPTX codex/pptx-fidelity-20260908 now advances through d08fa9e (JPEG picture import) to 4a3a6e3 (dimension precision) in /private/tmp/opf-pptx-fidelity/opf-pptx. A direct audit found that every exported JPEG EXIF orientation imported as orientation 1 because the native rotation/mirror was discarded. The importer now combines existing JPEG EXIF orientation with native quarter-turns/flips and writes the resulting orientation into a copied EXIF record, without decoding/recompressing pixels. Existing metadata is applied before the native transform; that is the importer contract, not a claim about every external viewer. All eight orientations emitted by this exporter recover exact source JPEG bytes through repeated fit-mode round-trips. Alternative text and input PPTX bytes are preserved.\n\nJPEGs without an orientation tag receive a minimal EXIF segment, or a replacement IFD0 appended within the existing bounded APP1 segment. Original metadata entries, referenced offsets and the next-IFD link remain intact. Both byte orders and malformed metadata are covered. Native crop windows are still not represented in the imported asset, and non-quarter-turn/non-JPEG transforms are unsupported. These cases now report unsupported-image-crop or unsupported-image-orientation through fromPptx's optional onDiagnostic callback, using native picture-order paths such as slides.0.pictures.0. Import still recomposes layout and is not a lossless picture/placement/crop/effect/group round trip.\n\nA complete image-slide preview comparison found a separate defect: emuToInches rounded native dimensions to six decimals, changing a 1280-pixel canvas to 1279.999968 pixels and perturbing raster edges. 4a3a6e3 removes that premature rounding. With the local JPEG-aware renderer, all eight complete fit-mode image-slide PNGs now match their source OPF PNG bytes after native export/import. This compares OPF-rendered previews; it does not compare native PowerPoint pixels. One imported orientation-7 PNG was visually inspected.\n\nNode 20.20.2 and 24.20.0 complete suites, syntax and metadata checks pass, including the final precision change (artifacts/image-import/node20-precision.log and node24-precision.log). Forty-two principal image-import cases cover all eight orientations in fit/crop, 16 native quarter-turn/flip combinations, existing EXIF plus native rotation, metadata insertion/link preservation, exact source bytes, independent pixel permutations, malformed EXIF and diagnostics. The old implementation fails the exact-JPEG round-trip assertion; artifacts/image-import/before.json records its eight incorrect orientation results. The 126-deck/805-slide structural corpus remains green with documented font and image substitutions.\n\nLatest local tarball: /private/tmp/opf-pptx-fidelity/packed-import-precision/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 88a32daefda4524161c87cfc5adaa5270b3cf68a. Fresh consumer /private/tmp/opf-pptx-fidelity/consumer-import-precision installs it with local renderer 87663fb and published core 0.4.0/editor 0.1.1/CLI 0.1.0. Installed picture-import and background tests pass. verify-preview.mjs records all eight exact whole-slide PNG comparisons, editor import and the combined browser dependency boundary; results are in preview/report.json and preview-verification.log. No browser UI or native viewer run was performed for this milestone. Historical d08fa9e tarball SHA-1 3af059814d4f22ae2b992228b75b8659d9427756 remains in packed-image-import; it lacks the subsequent precision fix.\n\nBoth changes are local/unpublished, still using development version 0.1.0. The four follow-up draft PR/push request remains pending. The native gradient visual discrepancy and locked-desktop Keynote review remain unresolved; no publication or new native-compatibility claim follows from this checkpoint.\n\n\n## Native Keynote background verification and merge preparation\n\nPPTX commit 821c047 resolves the earlier native gradient verification gap. Keynote 14.4 displays editable Advanced Gradient Fill controls and exports the six landscape angles correctly. Twelve unchanged native PNGs cover those opaque angles and six portrait solid/gradient transparency cases. Eighteen comparisons, including the native Keynote PPTX reimport, pass: opaque maximum channel error 4/255 and mean below 0.38; transparent alpha error at most 1/255. Quick Look remains a thumbnail limitation, not evidence that Keynote renders these gradients flat. Microsoft PowerPoint remains unverified. Generated documents were closed without saving.\n\nThe Keynote round-trip also exposed synthetic titles added to empty/background-only slides. Import now preserves blank content and notes-only slides. Project-authored native fixtures and their hashes are checked in; normal CI does not require Keynote. The historical importer fails the blank-slide assertion. Final Node 20/24 complete suites, syntax, package metadata and browser dependency checks pass, including the 126-deck/805-slide structural corpus with previously documented substitutions. Logs are artifacts/backgrounds/native-node20.log and native-node24.log in the PPTX worktree.\n\nThe user has authorized pushing, PR updates and merging the four prepared follow-ups. Merge preparation covers PPTX 821c047, renderer 87663fb, site 2858456 and this core example/handoff branch. These changes do not publish new npm versions; the existing development version numbers must not overwrite registry releases. Historical pending-approval and locked-desktop notes above describe earlier checkpoints and are superseded here.\n\n## Published fidelity releases and registry verification\n\nThe follow-up implementations are merged and now published through trusted publishing with npm provenance:\n\n| Package | Version | Tagged source | Publish workflow |\n| --- | --- | --- | --- |\n| @openpresentation/opf | 0.4.1 | aed5e5493998a5081fea68bf3bd42c52409e0c15 | 34204122730 |\n| @openpresentation/cli | 0.1.1 | aed5e5493998a5081fea68bf3bd42c52409e0c15 | 34204125018 |\n| @openpresentation/opf-render | 0.2.0 | df7ce5c6915a084f101adf90e6329117c0094a23 | 34204875207 |\n| @openpresentation/opf-pptx | 0.2.0 | 1fef9dcfadeeb8310b3c3b668f7506b52717b695 | 34205556749 |\n| @openpresentation/opf-editor | 0.1.2 | 5819a2ac12ec22f08a348c81bc3c66630682ecfb | 34205593335 |\n\nCore/CLI release PR 18, renderer PR 3, PPTX PR 3 and editor PR 2 were merged after Node 20/24 CI and automated review. Their merge commits passed CI before tags were pushed. GitHub releases contain matching changelog notes. Registry propagation briefly delayed PPTX availability; publication was not rerun, and the subsequent exact-version install passed.\n\nRenderer/PPTX 0.2.0 require Node 20.9 or later and core 0.4.1. Editor 0.1.2 accepts renderer 0.1.1 or 0.2.x through its optional peer; its development dependency explicitly installs renderer 0.2.0 for CI. The renderer release uses the individually reviewed corrected-image baseline and retains the preceding core-0.4.0 manifest for audit. Only the repaired example slide changes; the other 804 raster hashes remain identical.\n\nThe clean registry consumer at artifacts/npm/registry-consumer installs all five exact release-plan versions without local package overrides. Model/API, TypeScript, browser bundling and CLI checks pass. The actual registry CLI reports 0.1.1 with bundled OPF 0.4.1 and passes 60 command checks. Eight complete JPEG image-slide previews are byte-identical before and after PPTX export/import using the installed registry packages.\n\nThe new pnpm test:registry-fidelity command runs immutable test/fixture snapshots against installed npm dist files, checks registry lock records and real paths, and rejects local package overrides. It passes 23 WebP and 24 JPEG raster/PDF cases, all 805 raster baselines, 18 captured Keynote comparisons, table/image/background/import tests and the 126-deck/805-slide structural corpus with its documented fallback fonts and 26 synthetic image substitutions. Coordinated CI now pins the release commits and also runs the exact-version registry integration and fidelity checks on Node 20/24; full core history makes the pinned browser harness source available.\n\nBrowser execution against the registry packages passes 176 editor harness checks plus 13 WebP and 16 JPEG checks. JPEG comparison retains the previously documented aggregate bounds (maximum channel 49, mean below 0.616, at most 1.225% of channels differing by more than 10); it is not pixel-identical browser output. These harnesses do not establish real OS IME, cross-browser behavior or Microsoft PowerPoint fidelity. Core remains free and provider-neutral; normal CI needs no native presentation application.\n\nGallery and site release worktrees are /private/tmp/opf-release-gallery/pptx-gallery and /private/tmp/opf-release-site/openpresentation-site on codex/fidelity-release-20260908. Their locked core is 0.4.1, and editor/showcase assets are regenerated from the five-package registry consumer with version/integrity/hash manifests. All 854 gallery examples validate/render, 106 gallery tests pass, and both production builds pass. The site syncs opf-v0.4.1 and exposes 583 raw files/six skills. Public deployment verification follows these prepared updates; earlier production manifests remain historical until those PRs merge.\n\n## Shared table rows and native rich-table import releases\n\nThe current coordinated package set is core 0.6.0, CLI 0.3.0, renderer 0.4.0, PPTX 0.4.0 and editor 0.3.0. `release-plan.json` pins all five versions and their immutable source commits. Core PR 23, renderer PR 5, PPTX PR 8 and editor PR 4 merged after Node 20/24 CI and automated review. Each merged tree matches the tested head. All five packages were published through trusted publishing with provenance, and registry tarball integrities match the tested release candidates.\n\nCore exports `layoutTable` for shared content-aware row geometry. Rows use spare height before shrinking text; rich cell lines reserve uniform native paragraph advances. Renderer and PPTX consume the same row/cell boxes and fitted text, while editor 0.3.0 aligns dependency minima. PPTX imports supported native rich table character styles, paragraph defaults, theme fonts/colors, external links, run/field/break order and significant whitespace directly from XML. Unstyled body cells remain strings. Cached display text does not reconstruct original scalar types or live fields.\n\nFinal PPTX/editor candidate tarballs with published core/renderer dependencies passed native import and row geometry tests on Node 20/24, 15 browser import/containment checks and 14 browser editor checks. Full standalone tests retained the 126-deck/805-slide structural corpus and all 805 renderer raster baselines. These checks do not establish Microsoft PowerPoint raster parity, complete conditional table styles/merged geometry/cell decoration, cross-engine editing or real OS IME support.\n\nCore publication run 34228179949 published npm successfully but its later GitHub release step failed because a matching release already existed. Preserve existing release notes when the workflow runs again; only create a GitHub release when absent. Do not republish the existing npm version. CLI run 34228594104, renderer run 34229511905, editor run 34230608709 and PPTX run 34231015995 succeeded. PPTX package-index propagation briefly delayed normal clean installation after the exact-version endpoint was available; publication was not repeated.\n\nThe gallery and public-site rollout uses isolated `codex/table-layout-registry-20260908` branches in `/private/tmp/opf-release-gallery/pptx-gallery` and `/private/tmp/opf-release-site/openpresentation-site`. Their core manifests and locks are updated to 0.6.0. Regenerate editor/showcase assets from the verified registry consumer, pin site documentation to the reviewed coordination commit, then verify production deployments and bytes. Earlier public deployment evidence remains historical until those updates land.\n"
145
+ "markdown": "# Continue the OPF ecosystem work\n\nCurrent entrypoint: [September 29 review checkpoint](handoff-2026-09-29.md), the [font fidelity program](programs/font-fidelity-everywhere/README.md), and the [compatibility matrix](compatibility-matrix.md). The [September 21 handoff](handoff-2026-09-21.md) and dated checkpoints below are historical. Keep published-package evidence separate from subsequent source merges and unfinished acceptance.\n\nRuntime update (September 10): use **Node 24 only** for new development and verification; see [migration instructions](migrations/node24.md). Historical Node 20/24 results and commands below describe prior checkpoints. Keep distinct browser/OS/native gates and the existing Office recovery prerequisite.\n\nThe coordinated ecosystem PRs were merged on September 8, 2026 UTC. Continue from `main` in these repositories:\n\n| Checkout | Merged PR |\n| --- | --- |\n| opf | https://github.com/OpenPresentation/opf/pull/9 |\n| opf-render | https://github.com/OpenPresentation/opf-render/pull/1 |\n| opf-editor | https://github.com/OpenPresentation/opf-editor/pull/1 |\n| opf-pptx | https://github.com/OpenPresentation/opf-pptx/pull/1 |\n| pptx-gallery | https://github.com/Data-Advantage/pptx-gallery/pull/9 |\n| openpresentation-site | https://github.com/Data-Advantage/openpresentation-site/pull/5 |\n\nClone the six repositories into sibling directories. Use Node.js 24 and pnpm 10.33.2. From the parent directory:\n\n```sh\ngh repo clone OpenPresentation/opf -- --branch main\ngh repo clone OpenPresentation/opf-render -- --branch main\ngh repo clone OpenPresentation/opf-editor -- --branch main\ngh repo clone OpenPresentation/opf-pptx -- --branch main\ngh repo clone Data-Advantage/pptx-gallery -- --branch main\ngh repo clone Data-Advantage/openpresentation-site -- --branch main\n```\n\nInstall dependencies with `pnpm install --frozen-lockfile` in `opf`, `pptx-gallery` and `openpresentation-site`; use `npm ci` in the three library repositories. Then, from `opf`:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm test\npnpm test:skills\npnpm test:ecosystem\npnpm test:layout\npnpm test:lists\npnpm demo:editor\npnpm pack:ecosystem\npnpm test:packed-ecosystem\n```\n\nThe link step builds the sibling libraries against the current core. Re-run it after reinstalling dependencies. Core 0.4.0, renderer/editor 0.1.1 and PPTX/CLI 0.1.0 are now published. The source-link workflow remains useful for development. Generated review artifacts, installed dependencies and local server state are excluded from Git and rebuilt by these commands.\n\nStart the editor with `python3 -m http.server 3102 --directory artifacts/editor` from `opf`. In another terminal start the gallery with `OPF_LOCAL_WORKSPACE=1 pnpm dev --port 3101` from `pptx-gallery`. From `openpresentation-site`, run `OPF_LOCAL_SOURCE=../opf pnpm sync:opf`, then `pnpm dev --port 3103`.\n\nFor public-site documentation built from a remote branch instead of the sibling checkout, use `OPF_REPO_REF=codex/opf-ecosystem-20260907 OPF_FORCE_SYNC=1 pnpm sync:opf`.\n\nProduction builds use `OPF_LOCAL_WORKSPACE=1 pnpm build` in `pptx-gallery` after linking, and `OPF_LOCAL_SOURCE=../opf pnpm build` in `openpresentation-site`. The gallery workspace flag lets Turbopack resolve the sibling package. Normal standalone gallery builds now use registry core 0.4.0. Normal site builds default to the source tag matching the installed core version; stale/local snapshots refresh automatically unless OPF_LOCAL_SOURCE explicitly selects local development.\n\n## Current release checkpoint \u2014 September 8 UTC\n\nPR #8 is incorporated into the pushed PR #9 branch through merge 10ed11c. Both test suites, structural package slimming, named schema definitions, catalog/index fixes, governance and Node 20/24 release gates are retained. All six PRs are merged with merge commits; GitHub also marked PR #8 merged through its preserved ancestry. Both sites deployed automatically from the merged main branches.\n\nAll five planned versions are now published and resolve through ordinary npm installation:\n\n| Package | Version | Source and publication |\n| --- | --- | --- |\n| @openpresentation/opf | 0.4.0 | Tag opf-v0.4.0 at 6180096; workflow 34182120112 with provenance |\n| @openpresentation/opf-render | 0.1.1 | Tag opf-render-v0.1.1 at de53df7; workflow 34184779283 with provenance |\n| @openpresentation/opf-editor | 0.1.1 | Tag opf-editor-v0.1.1 at dbbe1a1; workflow 34184868180 with provenance |\n| @openpresentation/opf-pptx | 0.1.0 | Tag opf-pptx-v0.1.0 at 7a385fc; retried workflow 34182814991 with provenance |\n| @openpresentation/cli | 0.1.0 | Reviewed standalone tarball from core 6180096, authenticated first publication; no provenance on this bootstrap release |\n\nThe user completed npm login and browser 2FA. Trusted publishers now exist for editor/PPTX release.yml and core cli-publish.yml. Initial permissions failures were resolved, and exact tagged jobs reran successfully. The CLI public tarball SHA-1 is 0308493d1d85ce18518b69882085419ec3817d73, matching the reviewed artifact. npm metadata took several minutes to expose the new package; it now installs normally. Do not republish an existing version.\n\n`pnpm test:registry-ecosystem` passed for all five exact versions with no local overrides: model operations, fonts, SVG, editable PPTX, TypeScript, browser bundle, CLI create/validate/version. All 60 CLI command checks also pass using the registry-installed executable. `release-plan.json` pins immutable core/editor harness refs for the published release set so unreleased source tests cannot silently change release verification. `test:registry-libraries` is an additional four-library check, not a replacement for the complete gate. All six registry-installed browser suites pass 176 checks (34 canvas, 19 lists, 46 rich text, 19 layout, 18 blocks, 40 creation). Browser evidence lives in `artifacts/npm/registry-browser-verification.json`.\n\nPublished in 0.1.1: renderer 5472483 adds trace-only rich-line geometry; editor a4be429 adds native continuous rich typing with glyph-aligned caret/pointer selection, mixed-style preservation, draft undo/redo, cancellation, conflict protection and composition lifecycle handling. Source browser suites pass 176 checks (34 canvas, 19 lists, 46 rich text, 19 layout, 18 blocks, 40 creation). Actual keystrokes and a measured pointer hit were verified. Renderer/editor 0.1.1 passed standalone Node 20/24 CI (34184700599 and 34184801283), trusted publication, and clean five-package registry verification. Real OS IME, bidi/complex-script and cross-browser behavior remain open. Normal renderer output is unchanged and all 805 raster checks pass.\n\nThe renderer baseline covers 805 slides in 126 installed-core example decks; missing/changed corpora fail and updates create review candidates without replacing the baseline. All 17 overview sheets were inspected and timeline endpoint clipping was fixed. PptxGenJS remains pinned to 4.0.1; its unused image-size advisory remains unresolved, with model/image embedding tested while parser loading is blocked. Neither baseline nor model checks establish native PowerPoint fidelity.\n\nGallery review fix ee01bc5 passes production build and header geometry checks at 320, 640, 1024, 1279, 1280 and 1440 pixels. Phone/laptop screenshots and mobile search were checked. Site review fix fe638e8 removes duplicate sitemap routes, preserves root-relative guide links, omits catalogs without indexes and repairs code-block colors. Its prebuild regression suite covers 592 unique sitemap URLs, link resolution and missing/empty/legacy catalogs. All five confirmed review threads were resolved before merging.\n\nThe gallery editor and site showcase have now been regenerated from the exact npm set above. All 854 gallery documents validate and render using the installed packages; the schema reference exposes 604 fields. Checked-in manifests record package tarball URLs/integrities, example source refs and SHA-256 asset hashes. Normal production builds pass. Served checks pass for nine gallery documents, 583 site source hashes, six skills, seven guides and all refreshed editor/showcase asset hashes.\n\nReproduce registry assets after installing core build tooling and fetching the immutable harness/example refs in release-plan.json:\n\n```sh\npnpm test:registry-ecosystem\npnpm prepare:gallery:registry\npnpm build:showcase:registry\n```\n\nThe gallery command updates its checked-in editor/reference files. Copy the four files in artifacts/site-showcase to openpresentation-site/public/showcase, then build both sites. Registry checks generate their own browser HTML and font files; prior source-demo artifacts are no longer required. Source preparation removes any old registry manifest so it cannot falsely label a development bundle as published.\n\nFinal reviewed core ece9b90 passed core CI 34185231548 and coordinated CI 34185231533 on Node 20/24. The workflow pins renderer/editor release commits. Main now contains the complete ecosystem implementation and the 0.4.0 changelog correction. The user authorized pushes, PR updates, npm publication, and these six merges with their normal site deployment triggers. Preserve unrelated gallery pnpm-workspace.yaml.\n\n## Current scope and remaining work\n\nThe branch includes shared dynamic layout and pagination, loaded-font measurement and substitutes, rich text/lists, CSV/JSON import, an installable agent CLI, six portable skills, schema-driven properties, copy/import/galleries, canvas resizing/moving/creation/deletion, native PPTX improvements, and site/galleries integration. See [ecosystem quality](plans/ecosystem-quality.md) and [coverage](plans/spec-editor-coverage.md) for evidence and remaining fidelity gaps.\n\nThe broader goal is still active. Real OS IME/cross-browser typing, advanced table/media/preset fidelity, and native PowerPoint raster comparison remain work. Local preview tarballs are not evidence of registry publication.\n\n## Merged checkpoint and local follow-ups\n\n| Repository | Main merge commit |\n| --- | --- |\n| opf | c6323d9 |\n| opf-render | 371c6ce |\n| opf-editor | 33cfebc |\n| opf-pptx | e3afb70 |\n| pptx-gallery | 22f1748 |\n| openpresentation-site | b385495 |\n\nBoth production deployments are ready at www.pptx.gallery and www.openpresentation.org, from the exact merge commits above. Releases were already published before merging; no versions were republished.\n\nPost-merge checks passed: core 34186567364, coordinated packages 34186567415, renderer 34186581381, editor 34186583983, PPTX 34186586280 and gallery 34186589351. Live verification passed for 583 source-file hashes, six downloadable skills, seven guides, schema/text endpoints and nine schema-valid gallery documents. All seven gallery manifest hashes and three showcase hashes match production. The manifest's opf-spec.json is served at /api/opf-spec.json; the other editor assets are under /opf-editor/. The live editor renders six starter slides and opens its complete property controls.\n\nThe following local follow-ups remain outside this merged checkpoint. The PPTX table and image branches are now combined in the integration checkpoint below:\n\n- PPTX table fidelity: 7453c33 (c549049 fitting plus ba0ad8a import fixes, reconciled with main) on codex/table-export-fidelity-20260908 in /private/tmp/opf-table-export/opf-pptx. Native editable cells use shared loaded-font fitting, preserve nested minimum font sizes, fill uneven rows, and match the SVG border theme slot. Node 20/24 tests compare 168 cells, including 24 shrinking cases. Quick Look and Keynote can open the exports, but wrapping differs. Keynote's round-trip changes the specimen's 11.25/6.75-point text to 11/6 points; this is viewer-specific evidence, not PowerPoint verification.\n- Site snapshot source links: 2858456 (d5642af reconciled with main) on codex/site-snapshot-links-20260908 in /private/tmp/opf-site-snapshot/openpresentation-site. View-source links serve the exact documentation snapshot bytes instead of GitHub main. Regression tests, production build, 583 raw-file hashes, six skill downloads, seven guides and representative source links pass locally.\n\nNext: review the combined PPTX integration and separate site source-link change, then continue native PowerPoint comparison, real OS typing, and the remaining table/media/preset roadmap. The local commits are not published releases.\n\nThe table follow-up also preserves headerless first rows and empty rows on import using native firstRow flags. Node 20/24 tests and a clean local-tarball consumer pass. Keynote 14.4 recognizes zero headers/three rows versus one header/four rows in a two-slide specimen, with blank rows retained. Native PowerPoint remains untested. Local PR descriptions are ready; approval to open the two new PRs is pending.\n\n## Local image-fidelity follow-up\n\nCommit 0007ff1 on codex/image-fit-fidelity-20260908 in /private/tmp/opf-image-fit/opf-pptx is separate from the table branch. It preserves native picture proportions with fit/crop, honors slide overrides, and maps all eight JPEG EXIF orientations to native rotation/mirroring without recompressing pixels. PNG/JPEG/GIF/WebP dimensions come from the actual embedded bytes. Unsupported/unreadable dimensions produce a path-specific error. Node 20/24 suites pass with image-size loading blocked; 45 geometry cases, eight orientations in fit/crop, clean local-tarball installation and browser bundling pass. Keynote visually shows correct wide/tall PNG fit/crop and all eight JPEG orientations. Native PowerPoint, animated/vector media, non-JPEG orientation and lossless picture import remain open. The change is committed locally and unpublished.\n\n## Combined PPTX integration checkpoint\n\nLocal merge commit 4a41b2b on codex/pptx-fidelity-20260908 in /private/tmp/opf-pptx-fidelity/opf-pptx combines the table and image branches above. The original branches are preserved. An installed-example audit also exposed duplicate native object IDs on 23 slides; the integration repairs only duplicate IDs and adds an eight-slide mixed table/text/image/chart/list regression. Repeated multi-chart exports also exposed ZIP ordering differences when PptxGenJS counters cross digit widths; sorting after part-name normalization fixes them.\n\nNode 20 and 24 pass the integrated suites, syntax checks and package metadata validation. A new structural corpus gate exports/imports all 126 decks / 805 slides, checking slide XML, unique IDs, finite geometry, table grids and imported slide counts. It explicitly uses fallback fonts and 26 synthetic image substitutions. With original asset requests and fallback fonts, 102 decks export/import; the remaining 24 encounter missing local assets or a truncated PNG fixture. With strict available fonts and original assets, only one deck completes. These are different evidence levels: the complete structural gate is not proof of original-asset, typography or native-viewer fidelity.\n\nThe clean consumer in /private/tmp/opf-pptx-fidelity/consumer passes the complete suite against the installed local tarball plus published core 0.4.0 and renderer 0.1.1, including the structural corpus and a browser bundle.\n\nLocal packed artifact: /private/tmp/opf-pptx-fidelity/packed/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 ea3215e9cee82366e131e6b3d3115cd5e19a0b8e. Version 0.1.0 is unchanged for this unpublished development artifact; it must not overwrite the existing npm release. No follow-up branch has been pushed, no new PR opened, and no new package published. The earlier request to open follow-up PRs remains pending; the prepared PPTX description now covers this combined implementation.\n\n## Image media-type and example corrections\n\nPPTX integration now advances to 87f6616. Native raster file extensions and per-part content types are derived from actual embedded PNG/JPEG/GIF/WebP bytes, so transformed host assets and incorrect MIME/path hints cannot label JPEG pixels as PNG. Import detects these formats from bytes as well. Thirty-two cases cover byte results, typed/untyped result objects, data URIs, local paths and host paths/URIs, plus mislabeled older native files. Node 20/24 complete suites and package checks pass. The clean consumer at /private/tmp/opf-pptx-fidelity/consumer-87f6616 passes all tests, the 805-slide structural corpus and browser bundling with published core 0.4.0 / renderer 0.1.1. New local tarball: /private/tmp/opf-pptx-fidelity/packed-87f6616/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 15ce44e1ee1faed81aed711a3e3795a0974d7145. Earlier tarballs remain historical artifacts, and no new version is published.\n\nKeynote 14.4 directly opened the four-format specimen at artifacts/media-types/native-media-types.pptx in the integration worktree. PNG, JPEG and GIF show the expected quadrants and round circle. WebP imports as an empty rectangle, confirmed in both the slide overview and selected slide. That check established the need for compatible PNG fallback; the next checkpoint below implements and verifies it. Correct ZIP metadata alone was insufficient. No native PowerPoint installation is available. The generated specimen was closed without saving.\n\nCore local commit ef25735 replaces the eight-byte PNG signature in examples/technical/asset-source-forms.opf.json with the complete project-authored square PNG from the PPTX test fixtures. All 126 examples validate. The corrected example exports/imports four slides with its exact PNG bytes retained, and its SVG/PNG image slide was visually inspected using explicit fallback fonts. Core/CLI build verification includes checking the generated examples against this source. Other illustrative file/remote references remain host-supplied, not bundled. The correction is unreleased and requires a future core package/site snapshot update.\n\n## Compatible WebP checkpoint\n\nPPTX integration advances to local commit 8197854 on codex/pptx-fidelity-20260908. Default imageFormat: \"compatible\" converts WebP to static PNG after resolving the original asset once; fit/crop uses the decoded PNG dimensions. Pixel comparisons cover alpha, EXIF orientation/mirroring and the first animation frame. imageFormat: \"preserve\" retains unchanged WebP embedding. Conversion errors carry the OPF path, and compatible conversion has a 40-megapixel limit. Original source documents/bytes are unchanged; the exported picture contains PNG pixels, not original WebP metadata or animation.\n\nNode uses lazy Sharp 0.35.4 decoding, raising the package minimum to Node 20.9.0. Browser conditional imports select Blob/image/canvas decoding and exclude Sharp/Node code. Platform dependencies are present in the lockfile; npm's optional native dependencies must be installed normally. The audit still reports only the existing image-size/PptxGenJS advisories. esbuild 0.28.2 is a development-only dependency used to enforce the browser boundary.\n\nNode 20.20.2 and 24.20.0 complete suites, syntax and metadata checks pass. Thirty-six WebP cases match independent Pillow-decoded first-frame RGBA hashes; decoder-unavailable and malformed/oversize cases produce path-specific errors. Existing preservation tests explicitly select preserve mode. The 126-deck / 805-slide structural corpus remains green with its previously documented substitutions. Thirteen browser pixel/geometry/input checks pass, including alpha, EXIF, animation's first frame and DOM canvas output. npm test builds the browser verification page and asserts no native modules enter its bundle; actual browser execution remains a separate UI check.\n\nKeynote 14.4 now displays all six converted WebP specimens in /private/tmp/opf-pptx-fidelity/opf-pptx/artifacts/webp-fallback/compatible-webp.pptx. The earlier empty rectangles are gone; expected quadrants, circles, transparency and orientation are visible. Arial labels avoid the previous missing-font notice. The generated document was closed without saving. Microsoft PowerPoint and other browsers remain unverified, and SVG/vector assets and full animation playback remain separate work.\n\nLocal tarball /private/tmp/opf-pptx-fidelity/packed-8197854/openpresentation-opf-pptx-0.1.0.tgz has SHA-1 6c249cf52d0624c9408bd8abb4e544d444b20c3a. Clean consumer /private/tmp/opf-pptx-fidelity/consumer-8197854 installs published core 0.4.0 / renderer 0.1.1 and this tarball, and passes the complete model/image/corpus suite and browser bundle checks. The packed browser implementation also passes all 13 browser checks. Both decoder files and conditional package imports are packaged. This is unpublished development version 0.1.0; do not overwrite the existing registry release. No follow-up PRs, pushes or publications have been made.\n\n## Renderer WebP raster checkpoint\n\nLocal renderer commit 18c79e4 on codex/renderer-webp-20260908 in /private/tmp/opf-renderer-webp/opf-render fixes WebP images disappearing from PNG/PDF output. The new Node-only raster preparation decodes embedded WebP data URIs to static PNG; it leaves the source SVG/OPF unchanged and adds no external URL/path resolution. href/xlink:href, base64/percent encoding, XML character references, alpha, EXIF and the first animation frame are covered. Malformed input reports its data-opf-path when present or svg.images.N. Input format is checked before decoding and the 40-megapixel Sharp limit applies.\n\nNode 20.20.2 and 24.20.0 full suites, syntax/package checks and all 805 existing golden rasters pass; no baseline changed. Twenty-three focused cases inspect raster pixels, fit/crop, actual PDF image streams and PDF alpha masks. The historical renderer demonstrably fails the first independent pixel reference. Fixtures regenerate byte-for-byte with Pillow 12.3.0; the six-image overview PNG was visually inspected. PNG/PDF conversion uses lazy Sharp 0.35.4 and now requires Node 20.9+. The renderer audit reports zero advisories at this checkpoint. Browser SVG exports exclude the native raster adapter.\n\nThe local renderer tarball /private/tmp/opf-renderer-webp/packed/openpresentation-opf-render-0.1.1.tgz has SHA-1 d19e216a4e8ec0762ed9e912b0708002ec43be64. /private/tmp/opf-renderer-webp/consumer installs it with local PPTX 8197854 and published core 0.4.0, editor 0.1.1 and CLI 0.1.0. Both packages' focused/model/corpus checks pass, browser bundling excludes native decoders, editor import succeeds and CLI reports its expected versions. This is a mixed local/registry development consumer, not evidence of a new registry release.\n\nThe renderer branch and all earlier follow-ups remain local/unpublished. Prepared descriptions now cover four potential follow-up PRs: PPTX fidelity, renderer WebP raster output, the core example correction/handoff, and site snapshot source links. Existing examples/font substitutions and native Microsoft PowerPoint coverage remain separate limits. The JPEG PNG/PDF gap is addressed by the next checkpoint; other media/layout/editor requirements remain open.\n\n\n## Renderer JPEG orientation checkpoint\n\nLocal renderer commit 87663fb extends codex/renderer-webp-20260908 in /private/tmp/opf-renderer-webp/opf-render to honor JPEG EXIF orientations 2\u20138 in PNG/PDF output. It uses the existing lazy Sharp decoder and private rasterization copy. JPEGs with orientation 1 or no orientation retain their exact SVG attribute spelling and compressed bytes. Source OPF/SVG and browser SVG exports remain unchanged. Malformed JPEGs report the image path.\n\nNode 20.20.2 and 24.20.0 verification passes. The final Node 20 suite, syntax and metadata checks are recorded in artifacts/jpeg/node20-verification.log in the renderer worktree; all 805 golden rasters remain unchanged. Twenty-four focused cases cover all eight orientations, fit/crop, independent Pillow pixels (maximum channel difference 2), actual PDF RGB image streams, deterministic PNG output and errors. The historical renderer fails orientation 2 with a maximum channel error of 255. All 16 fixture JPEG/PNG files regenerate byte-for-byte using Pillow 12.3.0. The eight-image PNG overview was visually inspected.\n\nSixteen browser OPF-SVG orientation/fit/crop cases passed before the Mac locked, with an incorrect-orientation control. Maximum mean channel error was 0.6156662326388889, maximum fraction of channels differing by more than 10 was 1.2241753472222223%, and maximum individual channel difference was 49. The incorrect-orientation control had mean error 56.44899848090278. These are geometry/orientation checks with aggregate error bounds, not pixel-identical browser output. The checked-in harness is test/jpeg-browser.js; npm test builds it and asserts native raster code is absent. No additional browser run was performed after the Mac locked.\n\nPacked artifact: /private/tmp/opf-renderer-webp/packed-jpeg/openpresentation-opf-render-0.1.1.tgz, SHA-1 c239043a3230a9d4fb55f8213abde1d1ef12a7e8. A fresh /private/tmp/opf-renderer-webp/consumer-jpeg installs this tarball, local PPTX 8197854, and published core 0.4.0/editor 0.1.1/CLI 0.1.0. All 23 WebP and 24 JPEG focused cases pass against the installed renderer. Editable PPTX, SVG/PNG, editor import and the combined browser dependency boundary pass. The development artifact retains version 0.1.1; it is not a registry publication and must not overwrite that released version.\n\nGitHub was rechecked: all six coordinated PRs remain merged. The four follow-ups remain local, with the earlier request to push/open their draft PRs still pending. The renderer description now covers both WebP and JPEG raster fixes. Native Microsoft PowerPoint, additional browsers and remaining media/editor fidelity remain open.\n\n\n## Native background fill checkpoint\n\nLocal PPTX commit 4c06cf7 advances codex/pptx-fidelity-20260908 in /private/tmp/opf-pptx-fidelity/opf-pptx. Fixed solid and linear-gradient backgrounds now serialize as editable native p:bg fills rather than flattening gradients to a solid fallback. Solid opacity, hex alpha, gradient stop alpha/positions, deck defaults and slide overrides are retained. An inheritance test also exposed and fixed ignored inline theme overrides: theme objects now resolve their base record and then apply the inline properties.\n\nSVG's object-bounding-box gradient and DrawingML's unscaled slide-coordinate gradient need both direction and stop-interval conversion. The new background helper performs that conversion and native RGB solid/linear import without hidden OPF metadata. Native integer angles/positions incur rounding. Uniform alpha returns as background opacity; differing stop alpha returns as eight-bit RGBA, which may round. Unsupported native path gradients, transformed/theme stop colors, non-default tile/flip geometry and unrepresentable stop intervals produce unsupported-background-gradient through the new optional fromPptx onDiagnostic callback. Other background/decorative features remain open.\n\nFinal Node 20.20.2 and 24.20.0 full suites, syntax and metadata checks pass (artifacts/backgrounds/node20-final.log and node24-final.log in the PPTX worktree). Coverage includes 38 principal gradient/solid cases and 990 sample comparisons derived independently from serialized SVG endpoints and native physical gradient properties across landscape, portrait and square canvases. Additional cases cover theme inheritance, alpha, native edits, repeated export/import, empty/single/descending stops, malformed colors and unsupported native fills. The historical implementation fails the first editable-gradient assertion. The structural corpus still passes 126 decks / 805 slides using its documented fallback fonts and 26 synthetic image substitutions; that is not native raster parity evidence.\n\nNative visual verification is unresolved. Four Quick Look thumbnails (0, 30, 45 and 90 degrees) all contain the same flat RGB(106,176,222), rather than the intended gradient. The PNG files and corresponding PPTX/SVG references are under artifacts/backgrounds. Keynote UI inspection was attempted but the desktop tool reported the Mac locked; an unlock request is pending. Microsoft PowerPoint is unavailable. The native XML/mathematical checks must not be treated as proof of native appearance. This discrepancy needs investigation in a native viewer before a release decision.\n\nLocal package: /private/tmp/opf-pptx-fidelity/packed-backgrounds/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 ceacb66d28703b3de866db6847084df59b0dc893. Fresh consumer /private/tmp/opf-pptx-fidelity/consumer-backgrounds installs it with local renderer 87663fb plus published core 0.4.0, editor 0.1.1 and CLI 0.1.0. Installed gradient checks, all 47 JPEG/WebP raster cases, editor import and the combined browser dependency boundary pass. The package still has development version 0.1.0 and must not overwrite the existing registry release. No follow-up pushes, PRs or publications occurred.\n\n\n## JPEG picture import and dimension precision checkpoint\n\nPPTX codex/pptx-fidelity-20260908 now advances through d08fa9e (JPEG picture import) to 4a3a6e3 (dimension precision) in /private/tmp/opf-pptx-fidelity/opf-pptx. A direct audit found that every exported JPEG EXIF orientation imported as orientation 1 because the native rotation/mirror was discarded. The importer now combines existing JPEG EXIF orientation with native quarter-turns/flips and writes the resulting orientation into a copied EXIF record, without decoding/recompressing pixels. Existing metadata is applied before the native transform; that is the importer contract, not a claim about every external viewer. All eight orientations emitted by this exporter recover exact source JPEG bytes through repeated fit-mode round-trips. Alternative text and input PPTX bytes are preserved.\n\nJPEGs without an orientation tag receive a minimal EXIF segment, or a replacement IFD0 appended within the existing bounded APP1 segment. Original metadata entries, referenced offsets and the next-IFD link remain intact. Both byte orders and malformed metadata are covered. Native crop windows are still not represented in the imported asset, and non-quarter-turn/non-JPEG transforms are unsupported. These cases now report unsupported-image-crop or unsupported-image-orientation through fromPptx's optional onDiagnostic callback, using native picture-order paths such as slides.0.pictures.0. Import still recomposes layout and is not a lossless picture/placement/crop/effect/group round trip.\n\nA complete image-slide preview comparison found a separate defect: emuToInches rounded native dimensions to six decimals, changing a 1280-pixel canvas to 1279.999968 pixels and perturbing raster edges. 4a3a6e3 removes that premature rounding. With the local JPEG-aware renderer, all eight complete fit-mode image-slide PNGs now match their source OPF PNG bytes after native export/import. This compares OPF-rendered previews; it does not compare native PowerPoint pixels. One imported orientation-7 PNG was visually inspected.\n\nNode 20.20.2 and 24.20.0 complete suites, syntax and metadata checks pass, including the final precision change (artifacts/image-import/node20-precision.log and node24-precision.log). Forty-two principal image-import cases cover all eight orientations in fit/crop, 16 native quarter-turn/flip combinations, existing EXIF plus native rotation, metadata insertion/link preservation, exact source bytes, independent pixel permutations, malformed EXIF and diagnostics. The old implementation fails the exact-JPEG round-trip assertion; artifacts/image-import/before.json records its eight incorrect orientation results. The 126-deck/805-slide structural corpus remains green with documented font and image substitutions.\n\nLatest local tarball: /private/tmp/opf-pptx-fidelity/packed-import-precision/openpresentation-opf-pptx-0.1.0.tgz, SHA-1 88a32daefda4524161c87cfc5adaa5270b3cf68a. Fresh consumer /private/tmp/opf-pptx-fidelity/consumer-import-precision installs it with local renderer 87663fb and published core 0.4.0/editor 0.1.1/CLI 0.1.0. Installed picture-import and background tests pass. verify-preview.mjs records all eight exact whole-slide PNG comparisons, editor import and the combined browser dependency boundary; results are in preview/report.json and preview-verification.log. No browser UI or native viewer run was performed for this milestone. Historical d08fa9e tarball SHA-1 3af059814d4f22ae2b992228b75b8659d9427756 remains in packed-image-import; it lacks the subsequent precision fix.\n\nBoth changes are local/unpublished, still using development version 0.1.0. The four follow-up draft PR/push request remains pending. The native gradient visual discrepancy and locked-desktop Keynote review remain unresolved; no publication or new native-compatibility claim follows from this checkpoint.\n\n\n## Native Keynote background verification and merge preparation\n\nPPTX commit 821c047 resolves the earlier native gradient verification gap. Keynote 14.4 displays editable Advanced Gradient Fill controls and exports the six landscape angles correctly. Twelve unchanged native PNGs cover those opaque angles and six portrait solid/gradient transparency cases. Eighteen comparisons, including the native Keynote PPTX reimport, pass: opaque maximum channel error 4/255 and mean below 0.38; transparent alpha error at most 1/255. Quick Look remains a thumbnail limitation, not evidence that Keynote renders these gradients flat. Microsoft PowerPoint remains unverified. Generated documents were closed without saving.\n\nThe Keynote round-trip also exposed synthetic titles added to empty/background-only slides. Import now preserves blank content and notes-only slides. Project-authored native fixtures and their hashes are checked in; normal CI does not require Keynote. The historical importer fails the blank-slide assertion. Final Node 20/24 complete suites, syntax, package metadata and browser dependency checks pass, including the 126-deck/805-slide structural corpus with previously documented substitutions. Logs are artifacts/backgrounds/native-node20.log and native-node24.log in the PPTX worktree.\n\nThe user has authorized pushing, PR updates and merging the four prepared follow-ups. Merge preparation covers PPTX 821c047, renderer 87663fb, site 2858456 and this core example/handoff branch. These changes do not publish new npm versions; the existing development version numbers must not overwrite registry releases. Historical pending-approval and locked-desktop notes above describe earlier checkpoints and are superseded here.\n\n## Published fidelity releases and registry verification\n\nThe follow-up implementations are merged and now published through trusted publishing with npm provenance:\n\n| Package | Version | Tagged source | Publish workflow |\n| --- | --- | --- | --- |\n| @openpresentation/opf | 0.4.1 | aed5e5493998a5081fea68bf3bd42c52409e0c15 | 34204122730 |\n| @openpresentation/cli | 0.1.1 | aed5e5493998a5081fea68bf3bd42c52409e0c15 | 34204125018 |\n| @openpresentation/opf-render | 0.2.0 | df7ce5c6915a084f101adf90e6329117c0094a23 | 34204875207 |\n| @openpresentation/opf-pptx | 0.2.0 | 1fef9dcfadeeb8310b3c3b668f7506b52717b695 | 34205556749 |\n| @openpresentation/opf-editor | 0.1.2 | 5819a2ac12ec22f08a348c81bc3c66630682ecfb | 34205593335 |\n\nCore/CLI release PR 18, renderer PR 3, PPTX PR 3 and editor PR 2 were merged after Node 20/24 CI and automated review. Their merge commits passed CI before tags were pushed. GitHub releases contain matching changelog notes. Registry propagation briefly delayed PPTX availability; publication was not rerun, and the subsequent exact-version install passed.\n\nRenderer/PPTX 0.2.0 require Node 20.9 or later and core 0.4.1. Editor 0.1.2 accepts renderer 0.1.1 or 0.2.x through its optional peer; its development dependency explicitly installs renderer 0.2.0 for CI. The renderer release uses the individually reviewed corrected-image baseline and retains the preceding core-0.4.0 manifest for audit. Only the repaired example slide changes; the other 804 raster hashes remain identical.\n\nThe clean registry consumer at artifacts/npm/registry-consumer installs all five exact release-plan versions without local package overrides. Model/API, TypeScript, browser bundling and CLI checks pass. The actual registry CLI reports 0.1.1 with bundled OPF 0.4.1 and passes 60 command checks. Eight complete JPEG image-slide previews are byte-identical before and after PPTX export/import using the installed registry packages.\n\nThe new pnpm test:registry-fidelity command runs immutable test/fixture snapshots against installed npm dist files, checks registry lock records and real paths, and rejects local package overrides. It passes 23 WebP and 24 JPEG raster/PDF cases, all 805 raster baselines, 18 captured Keynote comparisons, table/image/background/import tests and the 126-deck/805-slide structural corpus with its documented fallback fonts and 26 synthetic image substitutions. Coordinated CI now pins the release commits and also runs the exact-version registry integration and fidelity checks on Node 20/24; full core history makes the pinned browser harness source available.\n\nBrowser execution against the registry packages passes 176 editor harness checks plus 13 WebP and 16 JPEG checks. JPEG comparison retains the previously documented aggregate bounds (maximum channel 49, mean below 0.616, at most 1.225% of channels differing by more than 10); it is not pixel-identical browser output. These harnesses do not establish real OS IME, cross-browser behavior or Microsoft PowerPoint fidelity. Core remains free and provider-neutral; normal CI needs no native presentation application.\n\nGallery and site release worktrees are /private/tmp/opf-release-gallery/pptx-gallery and /private/tmp/opf-release-site/openpresentation-site on codex/fidelity-release-20260908. Their locked core is 0.4.1, and editor/showcase assets are regenerated from the five-package registry consumer with version/integrity/hash manifests. All 854 gallery examples validate/render, 106 gallery tests pass, and both production builds pass. The site syncs opf-v0.4.1 and exposes 583 raw files/six skills. Public deployment verification follows these prepared updates; earlier production manifests remain historical until those PRs merge.\n\n## Shared table rows and native rich-table import releases\n\nThe current coordinated package set is core 0.6.0, CLI 0.3.0, renderer 0.4.0, PPTX 0.4.0 and editor 0.3.0. `release-plan.json` pins all five versions and their immutable source commits. Core PR 23, renderer PR 5, PPTX PR 8 and editor PR 4 merged after Node 20/24 CI and automated review. Each merged tree matches the tested head. All five packages were published through trusted publishing with provenance, and registry tarball integrities match the tested release candidates.\n\nCore exports `layoutTable` for shared content-aware row geometry. Rows use spare height before shrinking text; rich cell lines reserve uniform native paragraph advances. Renderer and PPTX consume the same row/cell boxes and fitted text, while editor 0.3.0 aligns dependency minima. PPTX imports supported native rich table character styles, paragraph defaults, theme fonts/colors, external links, run/field/break order and significant whitespace directly from XML. Unstyled body cells remain strings. Cached display text does not reconstruct original scalar types or live fields.\n\nFinal PPTX/editor candidate tarballs with published core/renderer dependencies passed native import and row geometry tests on Node 20/24, 15 browser import/containment checks and 14 browser editor checks. Full standalone tests retained the 126-deck/805-slide structural corpus and all 805 renderer raster baselines. These checks do not establish Microsoft PowerPoint raster parity, complete conditional table styles/merged geometry/cell decoration, cross-engine editing or real OS IME support.\n\nCore publication run 34228179949 published npm successfully but its later GitHub release step failed because a matching release already existed. Preserve existing release notes when the workflow runs again; only create a GitHub release when absent. Do not republish the existing npm version. CLI run 34228594104, renderer run 34229511905, editor run 34230608709 and PPTX run 34231015995 succeeded. PPTX package-index propagation briefly delayed normal clean installation after the exact-version endpoint was available; publication was not repeated.\n\nThe gallery and public-site rollout uses isolated `codex/table-layout-registry-20260908` branches in `/private/tmp/opf-release-gallery/pptx-gallery` and `/private/tmp/opf-release-site/openpresentation-site`. Their core manifests and locks are updated to 0.6.0. Regenerate editor/showcase assets from the verified registry consumer, pin site documentation to the reviewed coordination commit, then verify production deployments and bytes. Earlier public deployment evidence remains historical until those updates land.\n"
104
146
  },
105
147
  {
106
148
  "slug": "how-opf-works",
107
149
  "file": "docs/how-opf-works.md",
108
150
  "title": "How OPF Works",
109
- "markdown": '# How OPF Works\n\nAn OPF document is one JSON file that answers three questions about a presentation:\n\n- **What does it say?** \u2014 `slides`, with content payloads and assets.\n- **Who is it for and why?** \u2014 `audience`, `purpose`, `tone`, `language`, and `narrative`.\n- **What should it look like?** \u2014 `design`, resolved through themes, color schemes, and font schemes.\n\nThe document records intent; an engine (a renderer, exporter, or editor) turns that intent into pixels or `.pptx` output. OPF deliberately stops at the format boundary: it never embeds OOXML, layout geometry, or renderer-specific state. You \u2014 or your agent \u2014 own the story, the data, and the ask; the format\'s job is to keep all of that readable, diffable, and out of `<p:sp>` tags.\n\n## Anatomy of a document\n\n```\nPresentation\n\u251C\u2500\u2500 identity ...... name, description, organization, speaker, author\n\u251C\u2500\u2500 intent ........ audience, purpose, tone, language, narrative, takeaway, duration\n\u251C\u2500\u2500 content ....... slides[]\n\u2502 \u251C\u2500\u2500 title / subtitle / tag / notes / section / beat / layout\n\u2502 \u2514\u2500\u2500 one content shape:\n\u2502 root payload (a single content kind)\n\u2502 blocks[] (ordered payloads, placement inferred)\n\u2502 region keys (3x3 placement grid)\n\u251C\u2500\u2500 design ........ theme, colorScheme, fontScheme, background, logo, header, footer\n\u251C\u2500\u2500 assets ........ named media sources, referenced as "asset:<id>"\n\u2514\u2500\u2500 catalogs ...... per-kind overrides: inline records and/or custom sources\n```\n\nOnly `slides` is required. The smallest valid document:\n\n```json\n{\n "name": "Minimal OPF Deck",\n "slides": [\n { "title": "Minimal OPF Deck" },\n { "title": "Next Steps", "text": "Use this as a starting point." }\n ]\n}\n```\n\nEverything else in the format is optional and additive.\n\n## Slides and content\n\nA slide carries its content in one of three shapes. Pick the loosest shape that says what you mean \u2014 engines handle placement.\n\n**1. Root payload** \u2014 one content kind directly on the slide. The kind is inferred from the field present (`text`, `items`, `chart`, `table`, `image`, `video`, `code`, `metric`, `quote`, `timeline`); see [`content-payloads.md`](./content-payloads.md) for the full table.\n\n```json\n{\n "title": "Operating Metric",\n "metric": { "value": "42%", "label": "Review cycle reduction", "trend": "up" }\n}\n```\n\nMultiple kinds at the slide root (with no explicit `type`, `blocks`, or regions) are shorthand for the equivalent `blocks`:\n\n```json\n{\n "title": "Habitat",\n "text": "Jaguars are strongly associated with water and dense cover.",\n "items": ["Rainforests and flooded wetlands", "Large defended territories"]\n}\n```\n\n**2. `blocks`** \u2014 an ordered list of payloads when a slide has several pieces of content but placement should stay renderer-inferred:\n\n```json\n{\n "title": "Customer Feedback",\n "blocks": [\n { "table": { "columns": ["Theme", "Mentions"], "rows": [["Speed", 42], ["Ease of use", 31]] } },\n { "quote": { "text": "The new workflow cut review time in half.", "attribution": "Operations Lead" } }\n ]\n}\n```\n\n**3. Promoted region keys** \u2014 a 3\xD73 placement grid when position matters:\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n\n A bare column key ("left") spans all three rows.\n A bare row key ("top") spans all three columns.\n Keys span neighbors with "+" and intersect rows with columns via ":".\n```\n\nThe spans compose into the slide shapes you actually want:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThat last shape in JSON:\n\n```json\n{\n "title": "Adoption Doubled",\n "top": { "text": "Adoption doubled while support load stayed flat." },\n "middle+bottom:left": { "metric": { "value": "2.1x", "label": "Adoption" } },\n "middle+bottom:center+right": {\n "chart": { "type": "line", "data": { "columns": ["Month", "Teams"], "rows": [["Jan", 12], ["Feb", 18]] } }\n }\n}\n```\n\nAnd the two-column shape from the grid above:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": { "table": { "columns": ["Metric", "Value"], "rows": [["Revenue", "$4.2M"]] } },\n "center+right": { "chart": { "type": "line", "data": { "columns": ["Month", "Revenue"], "rows": [["Jan", 3.4]] } } }\n}\n```\n\nRegion keys on one slide must not overlap, and regions cannot be mixed with a root payload. Slide-level strings `title`, `subtitle`, and `tag` sit alongside whichever content shape you use, and render into the matching placeholders of the resolved layout.\n\n## Layouts are hints, not contracts\n\n`Slide.layout` optionally references a record in the `layouts` catalog. The layout\'s placeholders describe what the layout *exposes* (a title slot, chart regions, image treatment) \u2014 they do not constrain what the slide may contain. This loose coupling is intentional:\n\n- A slide may use any region keys or payloads regardless of its declared layout. Validators do not error on a slide/layout mismatch.\n- When `layout` is omitted, engines infer one from the slide\'s payload or region keys.\n- Free-form layout names that don\'t resolve through any catalog fall through to engine-defined layouts.\n\nThe principle, used throughout OPF: **slides are the source of truth**. Layouts, narratives, and design records guide rendering; they never invalidate content.\n\n## Narrative is intent, not structure\n\n`narrative` declares the deck\'s story arc. It resolves to a record in the `narratives` catalog (e.g. `"classic-story"`, `"pitch-deck"`), each of which defines ordered **beats** \u2014 labeled segments of the arc such as `hook`, `problem`, `evidence`, `ask` \u2014 with optional slide-blueprint hints (`slideType`, `layoutHint`, `instructions`, `thoughtCues`).\n\nSlides opt into beats via `Slide.beat`. Nothing forces them to: validators warn on drift (orphan slides, unused beats) but never error.\n\n```json\n{\n "name": "Schema Pitch",\n "narrative": {\n "id": "technical-proof",\n "name": "Technical Proof",\n "beats": [\n { "id": "contract", "name": "Contract", "slideType": "text", "instructions": "State what stays stable." },\n { "id": "evidence", "name": "Evidence", "slideType": "chart" },\n { "id": "adoption", "name": "Adoption", "slideType": "list" }\n ]\n },\n "slides": [\n { "beat": "contract", "title": "The Contract", "text": "Beats describe intent without constraining slides." },\n { "beat": ["evidence", "adoption"], "title": "Proof And Ask", "items": ["One slide may cover several beats."] }\n ]\n}\n```\n\nObject form supports overrides: `{ "id": "classic-story", "beats": [...] }` merges inline beats into the catalog record by beat `id`. An object whose `id` matches no record \u2014 like `technical-proof` above \u2014 is a fully custom inline narrative. Deck-level concerns that aren\'t part of the storyline (`audience`, `tone`, `takeaway`, `duration`) live as siblings on the presentation root, not inside the narrative.\n\n## Catalog references and how they resolve\n\nMost reusable values in OPF are references into **catalogs**: named collections of records, each identified by a kebab-case `id`. The referencing fields are `narrative`, `language`, `tone`, `audience`, `purpose`, `design.theme`, `design.colorScheme`, `design.fontScheme`, `Slide.layout`, `Chart.type`, and the platform keys in `socials`.\n\nEvery reference resolves through the same chain, first match wins:\n\n```\n "design": { "colorScheme": "cool-horizon" }\n |\n v\n 1. catalogs.colorSchemes.records[] inline records in this document\n | miss\n v\n 2. catalogs.colorSchemes.source custom registry declared in this document\n | miss\n v\n 3. default catalog https://www.pptx.gallery/color-schemes\n | miss (bundled in spec/catalogs/ and in\n v the @openpresentation/opf package)\n validation warning \u2014 never an error \u2014 and an engine fallback\n```\n\nWhen a reference is omitted entirely, engines fall back to their own defaults (see [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json) for a reference example \u2014 that file is engine configuration, not part of the document contract).\n\nThree reference forms are accepted wherever a catalog reference is allowed:\n\n- **Bare id** for the common case: `"narrative": "classic-story"`.\n- **Object form** for catalog-backed overrides: `{ "id": "cool-horizon", "accent1": "#0F4C81" }` resolves the record as a base, then inline fields win per key.\n- **URL or `pkg:` reference**, which skips the catalog lookup and resolves directly.\n\nA document can carry its own records or point at a private registry, which also silences unknown-id warnings for that kind:\n\n```json\n{\n "name": "Branded Deck",\n "design": { "colorScheme": "acme-brand" },\n "catalogs": {\n "colorSchemes": {\n "records": [{ "id": "acme-brand", "accent1": "#0F4C81", "light1": "#FFFFFF", "dark1": "#0B1B2B" }]\n },\n "narratives": { "source": "https://catalogs.example.com/narratives" }\n },\n "slides": [{ "title": "Branded Deck" }]\n}\n```\n\n## Design in one paragraph\n\n`design` selects a `theme` (which bundles default color scheme, font scheme, background, and dimensions) and may override any of those directly; `Slide.design` overrides the deck design per slide. More specific always wins, field by field. Color schemes and font schemes each support two mixable models \u2014 OOXML slots/pairs that round-trip to PowerPoint, and abstract roles (`primary`, `heading`, `code`, \u2026) that engines map onto slots. The full precedence chain with worked examples is in [`design-resolution.md`](./design-resolution.md).\n\n## Assets\n\nBinary content lives in the top-level `assets` registry, keyed by id. Content payloads and design fields reference entries with `asset:<id>` strings; asset `src` values accept HTTPS URLs, data URIs, and paths resolved against the OPF file location.\n\n## A complete small deck\n\nEverything above, together \u2014 intent metadata, a catalog-backed narrative with beats, design, an organization and speaker, an asset-backed chart, regions, notes, and sections:\n\n```json\n{\n "$schema": "https://openpresentation.org/schema/opf/v1",\n "name": "Q3 Business Review",\n "description": "Quarterly review for the executive team.",\n "audience": "executives",\n "purpose": "decide",\n "tone": "formal",\n "language": "en-US",\n "narrative": "qbr",\n "takeaway": "Approve the expanded rollout budget.",\n "duration": 20,\n "organization": {\n "id": "acme",\n "name": "Acme Corp",\n "domain": "acme.com",\n "socials": { "linkedin": "acme" }\n },\n "speaker": { "id": "alice", "name": "Alice Chen", "title": "VP Operations", "organizationId": "acme" },\n "design": {\n "theme": "classic",\n "colorScheme": "forest-green",\n "footer": { "left": { "organization": true }, "right": { "slideNumber": true } }\n },\n "assets": {\n "adoption-csv": { "src": "./data/adoption.csv", "alt": "Monthly adoption data" }\n },\n "slides": [\n {\n "layout": "title",\n "beat": "objectives",\n "title": "Q3 Business Review",\n "subtitle": "Operations \u2014 October 2025"\n },\n {\n "beat": "performance-headline",\n "title": "Adoption Doubled",\n "left": { "metric": { "value": "2.1x", "label": "Quarter-over-quarter adoption", "trend": "up" } },\n "center+right": {\n "chart": { "type": "line", "data": { "src": "asset:adoption-csv", "columns": ["Month", "Active Teams"] } }\n },\n "notes": "Pause here; this is the slide the decision hangs on."\n },\n {\n "beat": "risks",\n "section": "Decision",\n "title": "What Could Go Wrong",\n "items": [\n "Capacity: two regions are at 85% utilization.",\n {\n "text": "Churn risk in the legacy tier.",\n "description": "Mitigation: migration incentives ship in November."\n }\n ]\n },\n {\n "beat": "asks",\n "title": "The Ask",\n "text": "Approve $1.2M to expand the rollout to all regions in Q4."\n }\n ]\n}\n```\n\nThe beat ids (`objectives`, `performance-headline`, `risks`, `asks`) come from the `qbr` narrative record; the theme, color scheme, chart type, and layout all resolve through the bundled catalogs. For a fixture that exercises the full surface in one file, see [`examples/technical/full-feature-tour.opf.json`](../examples/technical/full-feature-tour.opf.json).\n\n## Validation philosophy\n\nTwo layers, with a deliberate split:\n\n- **Schema errors** for structural problems: wrong types, overlapping region keys, payloads mixing incompatible content kinds, a region payload missing concrete content.\n- **Warnings** for advisory drift: unknown catalog ids, narrative/slide mismatches. These never make a document invalid.\n\n`validatePresentation` from `@openpresentation/opf` applies both layers locally.\n\n## Where to go next\n\n- [`schema-reference.md`](./schema-reference.md) \u2014 every field of every object in the presentation schema.\n- [`catalog-schema-reference.md`](./catalog-schema-reference.md) \u2014 every field of every catalog record schema.\n- [`content-payloads.md`](./content-payloads.md) \u2014 payload shapes and inference rules with examples.\n- [`design-resolution.md`](./design-resolution.md) \u2014 the design precedence algorithm.\n- [`examples.md`](./examples.md) \u2014 guide to the example decks under `examples/`.\n\nThen write a deck, commit it, revise it, and read the diff. A two-line diff for a two-word change is the whole argument for the format.\n'
151
+ "markdown": '# How OPF Works\n\nAn OPF document is one JSON file that answers three questions about a presentation:\n\n- **What does it say?** \u2014 `slides`, with content payloads and assets.\n- **Who is it for and why?** \u2014 `audience`, `purpose`, `tone`, `language`, and `narrative`.\n- **What should it look like?** \u2014 `design`, resolved through themes, color schemes, and font schemes.\n\nThe document records intent; an engine (a renderer, exporter, or editor) turns that intent into pixels or `.pptx` output. OPF deliberately stops at the format boundary: it never embeds OOXML, layout geometry, or renderer-specific state. You \u2014 or your agent \u2014 own the story, the data, and the ask; the format\'s job is to keep all of that readable, diffable, and out of `<p:sp>` tags.\n\n## Anatomy of a document\n\n```\nPresentation\n\u251C\u2500\u2500 identity ...... name, description, organization, speaker, author\n\u251C\u2500\u2500 intent ........ audience, purpose, tone, language, narrative, takeaway, duration\n\u251C\u2500\u2500 content ....... slides[]\n\u2502 \u251C\u2500\u2500 title / subtitle / tag / notes / section / beat / layout\n\u2502 \u2514\u2500\u2500 one content shape:\n\u2502 root payload (a single content kind)\n\u2502 blocks[] (ordered payloads, placement inferred)\n\u2502 region keys (3x3 placement grid)\n\u251C\u2500\u2500 design ........ theme, colorScheme, fontScheme, background, logo, header, footer\n\u251C\u2500\u2500 variables ..... named colors, referenced from content as "var:<id>"\n\u251C\u2500\u2500 assets ........ named media sources, referenced as "asset:<id>"\n\u2514\u2500\u2500 catalogs ...... per-kind overrides: inline records and/or custom sources\n```\n\nOnly `slides` is required. The smallest valid document:\n\n```json\n{\n "name": "Minimal OPF Deck",\n "slides": [\n { "title": "Minimal OPF Deck" },\n { "title": "Next Steps", "text": "Use this as a starting point." }\n ]\n}\n```\n\nEverything else in the format is optional and additive.\n\n## Slides and content\n\nA slide carries its content in one of three shapes. Pick the loosest shape that says what you mean \u2014 engines handle placement.\n\n**1. Root payload** \u2014 one content kind directly on the slide. The kind is inferred from the field present (`text`, `items`, `chart`, `table`, `image`, `video`, `code`, `metric`, `quote`, `timeline`); see [`content-payloads.md`](./content-payloads.md) for the full table.\n\n```json\n{\n "title": "Operating Metric",\n "metric": { "value": "42%", "label": "Review cycle reduction", "trend": "up" }\n}\n```\n\nMultiple kinds at the slide root (with no explicit `type`, `blocks`, or regions) are shorthand for the equivalent `blocks`:\n\n```json\n{\n "title": "Habitat",\n "text": "Jaguars are strongly associated with water and dense cover.",\n "items": ["Rainforests and flooded wetlands", "Large defended territories"]\n}\n```\n\n**2. `blocks`** \u2014 an ordered list of payloads when a slide has several pieces of content but placement should stay renderer-inferred:\n\n```json\n{\n "title": "Customer Feedback",\n "blocks": [\n { "table": { "columns": ["Theme", "Mentions"], "rows": [["Speed", 42], ["Ease of use", 31]] } },\n { "quote": { "text": "The new workflow cut review time in half.", "attribution": "Operations Lead" } }\n ]\n}\n```\n\n**3. Promoted region keys** \u2014 a 3\xD73 placement grid when position matters:\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n\n A bare column key ("left") spans all three rows.\n A bare row key ("top") spans all three columns.\n Keys span neighbors with "+" and intersect rows with columns via ":".\n```\n\nThe spans compose into the slide shapes you actually want:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThat last shape in JSON:\n\n```json\n{\n "title": "Adoption Doubled",\n "top": { "text": "Adoption doubled while support load stayed flat." },\n "middle+bottom:left": { "metric": { "value": "2.1x", "label": "Adoption" } },\n "middle+bottom:center+right": {\n "chart": { "type": "line", "data": { "columns": ["Month", "Teams"], "rows": [["Jan", 12], ["Feb", 18]] } }\n }\n}\n```\n\nAnd the two-column shape from the grid above:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": { "table": { "columns": ["Metric", "Value"], "rows": [["Revenue", "$4.2M"]] } },\n "center+right": { "chart": { "type": "line", "data": { "columns": ["Month", "Revenue"], "rows": [["Jan", 3.4]] } } }\n}\n```\n\nRegion keys on one slide must not overlap, and regions cannot be mixed with a root payload. Slide-level strings `title`, `subtitle`, and `tag` sit alongside whichever content shape you use, and render into the matching placeholders of the resolved layout.\n\n## Layouts are hints, not contracts\n\n`Slide.layout` optionally references a record in the `layouts` catalog. The layout\'s placeholders describe what the layout *exposes* (a title slot, chart regions, image treatment) \u2014 they do not constrain what the slide may contain. This loose coupling is intentional:\n\n- A slide may use any region keys or payloads regardless of its declared layout. Validators do not error on a slide/layout mismatch.\n- When `layout` is omitted, engines infer one from the slide\'s payload or region keys.\n- Free-form layout names that don\'t resolve through any catalog fall through to engine-defined layouts.\n\nThe principle, used throughout OPF: **slides are the source of truth**. Layouts, narratives, and design records guide rendering; they never invalidate content.\n\n## Narrative is intent, not structure\n\n`narrative` declares the deck\'s story arc. It resolves to a record in the `narratives` catalog (e.g. `"classic-story"`, `"pitch-deck"`), each of which defines ordered **beats** \u2014 labeled segments of the arc such as `hook`, `problem`, `evidence`, `ask` \u2014 with optional slide-blueprint hints (`slideType`, `layoutHint`, `instructions`, `thoughtCues`).\n\nSlides opt into beats via `Slide.beat`. Nothing forces them to: validators warn on drift (orphan slides, unused beats) but never error.\n\n```json\n{\n "name": "Schema Pitch",\n "narrative": {\n "id": "technical-proof",\n "name": "Technical Proof",\n "beats": [\n { "id": "contract", "name": "Contract", "slideType": "text", "instructions": "State what stays stable." },\n { "id": "evidence", "name": "Evidence", "slideType": "chart" },\n { "id": "adoption", "name": "Adoption", "slideType": "list" }\n ]\n },\n "slides": [\n { "beat": "contract", "title": "The Contract", "text": "Beats describe intent without constraining slides." },\n { "beat": ["evidence", "adoption"], "title": "Proof And Ask", "items": ["One slide may cover several beats."] }\n ]\n}\n```\n\nObject form supports overrides: `{ "id": "classic-story", "beats": [...] }` merges inline beats into the catalog record by beat `id`. An object whose `id` matches no record \u2014 like `technical-proof` above \u2014 is a fully custom inline narrative. Deck-level concerns that aren\'t part of the storyline (`audience`, `tone`, `takeaway`, `duration`) live as siblings on the presentation root, not inside the narrative.\n\n## Catalog references and how they resolve\n\nMost reusable values in OPF are references into **catalogs**: named collections of records, each identified by a kebab-case `id`. The referencing fields are `narrative`, `language`, `tone`, `audience`, `purpose`, `design.theme`, `design.colorScheme`, `design.fontScheme`, `Slide.layout`, `Chart.type`, and the platform keys in `socials`.\n\nEvery reference resolves through the same chain, first match wins:\n\n```\n "design": { "colorScheme": "cool-horizon" }\n |\n v\n 1. catalogs.colorSchemes.records[] inline records in this document\n | miss\n v\n 2. catalogs.colorSchemes.source custom registry declared in this document\n | miss\n v\n 3. default catalog https://www.pptx.gallery/color-schemes\n | miss (bundled in spec/catalogs/ and in\n v the @openpresentation/opf package)\n validation warning \u2014 never an error \u2014 and an engine fallback\n```\n\nWhen a reference is omitted entirely, engines fall back to their own defaults (see [`spec/reference/engine-defaults.json`](../spec/reference/engine-defaults.json) for a reference example \u2014 that file is engine configuration, not part of the document contract).\n\nThree reference forms are accepted wherever a catalog reference is allowed:\n\n- **Bare id** for the common case: `"narrative": "classic-story"`.\n- **Object form** for catalog-backed overrides: `{ "id": "cool-horizon", "accent1": "#0F4C81" }` resolves the record as a base, then inline fields win per key.\n- **URL or `pkg:` reference**, which skips the catalog lookup and resolves directly.\n\nA document can carry its own records or point at a private registry, which also silences unknown-id warnings for that kind:\n\n```json\n{\n "name": "Branded Deck",\n "design": { "colorScheme": "acme-brand" },\n "catalogs": {\n "colorSchemes": {\n "records": [{ "id": "acme-brand", "accent1": "#0F4C81", "light1": "#FFFFFF", "dark1": "#0B1B2B" }]\n },\n "narratives": { "source": "https://catalogs.example.com/narratives" }\n },\n "slides": [{ "title": "Branded Deck" }]\n}\n```\n\n## Design in one paragraph\n\n`design` selects a `theme` (which bundles default color scheme, font scheme, background, and dimensions) and may override any of those directly; `Slide.design` overrides the deck design per slide. More specific always wins, field by field. Color schemes and font schemes each support two mixable models \u2014 OOXML slots/pairs that round-trip to PowerPoint, and abstract roles (`primary`, `heading`, `code`, \u2026) that engines map onto slots. Content color fields (rich-text runs, styled table cells) reference the design system by name \u2014 a scheme slot (`accent2`), a role (`text`), or a `var:<id>` entry from the top-level `variables` map \u2014 so styled content follows a re-theme instead of freezing hex values. The full precedence chain with worked examples is in [`design-resolution.md`](./design-resolution.md).\n\n## Assets\n\nBinary content lives in the top-level `assets` registry, keyed by id. Content payloads and design fields reference entries with `asset:<id>` strings; asset `src` values accept HTTPS URLs, data URIs, and paths resolved against the OPF file location.\n\n## A complete small deck\n\nEverything above, together \u2014 intent metadata, a catalog-backed narrative with beats, design, an organization and speaker, an asset-backed chart, regions, notes, and sections:\n\n```json\n{\n "$schema": "https://openpresentation.org/schema/opf/v1",\n "name": "Q3 Business Review",\n "description": "Quarterly review for the executive team.",\n "audience": "executives",\n "purpose": "decide",\n "tone": "formal",\n "language": "en-US",\n "narrative": "qbr",\n "takeaway": "Approve the expanded rollout budget.",\n "duration": 20,\n "organization": {\n "id": "acme",\n "name": "Acme Corp",\n "domain": "acme.com",\n "socials": { "linkedin": "acme" }\n },\n "speaker": { "id": "alice", "name": "Alice Chen", "title": "VP Operations", "organizationId": "acme" },\n "design": {\n "theme": "classic",\n "colorScheme": "forest-green",\n "footer": { "left": { "organization": true }, "right": { "slideNumber": true } }\n },\n "assets": {\n "adoption-csv": { "src": "./data/adoption.csv", "alt": "Monthly adoption data" }\n },\n "slides": [\n {\n "layout": "title",\n "beat": "objectives",\n "title": "Q3 Business Review",\n "subtitle": "Operations \u2014 October 2025"\n },\n {\n "beat": "performance-headline",\n "title": "Adoption Doubled",\n "left": { "metric": { "value": "2.1x", "label": "Quarter-over-quarter adoption", "trend": "up" } },\n "center+right": {\n "chart": { "type": "line", "data": { "src": "asset:adoption-csv", "columns": ["Month", "Active Teams"] } }\n },\n "notes": "Pause here; this is the slide the decision hangs on."\n },\n {\n "beat": "risks",\n "section": "Decision",\n "title": "What Could Go Wrong",\n "items": [\n "Capacity: two regions are at 85% utilization.",\n {\n "text": "Churn risk in the legacy tier.",\n "description": "Mitigation: migration incentives ship in November."\n }\n ]\n },\n {\n "beat": "asks",\n "title": "The Ask",\n "text": "Approve $1.2M to expand the rollout to all regions in Q4."\n }\n ]\n}\n```\n\nThe beat ids (`objectives`, `performance-headline`, `risks`, `asks`) come from the `qbr` narrative record; the theme, color scheme, chart type, and layout all resolve through the bundled catalogs. For a fixture that exercises the full surface in one file, see [`examples/technical/full-feature-tour.opf.json`](../examples/technical/full-feature-tour.opf.json).\n\n## Validation philosophy\n\nTwo layers, with a deliberate split:\n\n- **Schema errors** for structural problems: wrong types, overlapping region keys, payloads mixing incompatible content kinds, a region payload missing concrete content, duplicate slide or payload ids.\n- **Warnings** for advisory drift: unknown catalog ids, unknown `var:` variable references and unrecognized run colors, narrative/slide mismatches. These never make a document invalid.\n\n`validatePresentation` from `@openpresentation/opf` applies both layers locally.\n\n## Where to go next\n\n- [`schema-reference.md`](./schema-reference.md) \u2014 every field of every object in the presentation schema.\n- [`catalog-schema-reference.md`](./catalog-schema-reference.md) \u2014 every field of every catalog record schema.\n- [`content-payloads.md`](./content-payloads.md) \u2014 payload shapes and inference rules with examples.\n- [`design-resolution.md`](./design-resolution.md) \u2014 the design precedence algorithm.\n- [`examples.md`](./examples.md) \u2014 guide to the example decks under `examples/`.\n\nThen write a deck, commit it, revise it, and read the diff. A two-line diff for a two-word change is the whole argument for the format.\n'
152
+ },
153
+ {
154
+ "slug": "image-treatments",
155
+ "file": "docs/image-treatments.md",
156
+ "title": "Image treatments",
157
+ "markdown": "# Image treatments\n\nThis page covers `design.slideImage`: the slide-level image and the treatments that both coordinated engines draw the same way. opf-render draws them as SVG and opf-pptx exports them as native PowerPoint DrawingML. It also maps each of the 15 [pptx.gallery image treatments](https://www.pptx.gallery/image-treatments) to OPF. Each mapping is either supported, partial or unsupported, with the reason. The reference deck [`fixtures/image-treatments.opf.json`](fixtures/image-treatments.opf.json) has one slide per gallery treatment, and `pnpm test:slide-images` checks it across core, SVG and PPTX.\n\n## When a slide image applies\n\nCore composition (`composeSlide`) resolves `design.slideImage` into `geometry.slideImage`. Both engines draw exactly that frame. It applies to a slide in three cases:\n\n- The slide sets its own `design.slideImage`.\n- The deck sets it and the slide's layout declares `slideImage: true`.\n- The deck sets it and the slide's root `image` is the same source.\n\nWhen the slide's root `image` is the same source, or when the treatment has no `src`, the root image becomes the slide image instead of a content item. Other slides ignore a deck-level value. See [dynamic composition](dynamic-composition.md#slide-level-images).\n\n## Treatment vocabulary\n\nEvery property is optional and additive on the `{ position, ... }` object form.\n\n| Property | Meaning | Native PPTX | SVG preview |\n|---|---|---|---|\n| `position` | `background`, or a band along `left`/`right`/`top`/`bottom`. Headings and content compose in the rest of the slide. | `p:pic` beneath all content | `<image>` beneath branding and content |\n| `size` | Band share of the slide, 0.1 to 0.9. Default 0.5. | frame geometry | frame geometry |\n| `inset` | Frame inside the slide padding, like a card. | frame geometry | frame geometry |\n| `aspectRatio` | Largest centered frame with this width/height ratio (`circle` uses 1). | frame geometry | frame geometry |\n| `fill` | `crop` covers the frame from the center; `fit` centers the whole image. Default `design.imageFill`, else `crop`. | `a:srcRect`: positive insets crop, negative insets pad | `preserveAspectRatio` `slice` / `meet` |\n| `shape` | `rectangle`, `rounded`, `circle`, `hexagon` | `a:prstGeom` `rect` / `roundRect` / `ellipse` / `hexagon` with core's guide values | `clipPath` with core's outline of the same preset formula |\n| `cornerRadius` | `rounded` radius as a share of the shorter side. Default 0.16667. | `roundRect` `adj` | circular arcs with the same radius |\n| `border` | `{ color, width }`. The line is centered on the outline, and its width is in reference pixels. | `a:ln` with `solidFill`, `algn=\"ctr\"` and a miter join | `stroke` on the same outline, miter join |\n| `opacity` | Image opacity, image pixels only. | `a:alphaModFix` | `opacity` on the image only |\n| `recolor` | `\"grayscale\"`, or `{ dark, light }` for duotone. Luminance uses Rec. 601 weights on sRGB. | `a:grayscl` / `a:duotone` | `feColorMatrix` in sRGB |\n| `overlay` | `{ color, opacity, edge?, size? }` scrim in the frame's shape, or an edge band on a rectangle frame. | one `p:sp` directly above the picture | `path` with `fill-opacity` |\n| `alt` | Alternative text. | `descr` | `aria-label` |\n\nColors accept the usual `ColorRef` forms: hex, scheme slot or role, or `var:<id>`. Eight-digit hex alpha is kept as native `a:alpha` and SVG opacity. The paint order is the same in both engines: image with its recolor and opacity, then the line, then the overlay. An edge overlay on a non-rectangle frame reports `unsupported-image-treatment` at `...slideImage.overlay.edge`, and neither engine draws it. That diagnostic never fails strict layout.\n\nAn unchanged export imports back as the slide's `design.slideImage`: the treatment, plus a data URI of the embedded image. `OPF_SLIDE_IMAGE_V1` and `OPF_SLIDE_IMAGE_OVERLAY_V1` shape tags record the native geometry. Changes are handled like this:\n\n- A picture that was moved, re-cropped or recolored stays an ordinary image and reports `invalid-slide-image-provenance`.\n- An edited overlay drops only the overlay.\n\n## pptx.gallery treatments\n\n| Gallery treatment | OPF `design.slideImage` (plus slide design) | Status | Reason or gap |\n|---|---|---|---|\n| full-bleed | `{position: background, fill: crop, overlay: {color: dark1, opacity: 0.2}}` | supported | |\n| text-overlay | `{position: background, fill: crop, overlay: {color: dark1, opacity: 0.55}}` | supported | Heading color still follows the theme background. Use a dark theme or background for light text over photos. |\n| side-by-side | `{position: left, size: 0.46, fill: crop}` | supported | |\n| caption-overlay | `{position: background, inset: true, fill: crop, overlay: {color: dark1, opacity: 0.75, edge: bottom, size: 0.25}}` | supported | Square corners. An edge band cannot follow a rounded mask as one native shape. |\n| masked-shape | `{position: right, inset: true, shape: hexagon, aspectRatio: 1.1547, fill: crop}` | supported | Masks are limited to the four presets. Arbitrary polygon and custom-geometry masks are not in the vocabulary. |\n| circular-crop | `{position: left, size: 0.4, inset: true, shape: circle, fill: crop}` | supported | The gallery's small shadow is not drawn (see shadows). |\n| rounded-card | `{position: background, inset: true, shape: rounded, cornerRadius: 0.05, fill: crop, border: {color: accent5, width: 1}}` | supported, without shadow | The optional card shadow is unsupported (see shadows). |\n| duotone | `{position: background, fill: crop, recolor: {dark: accent1, light: light1}}` | supported | Native raster parity of PowerPoint's luminance weights is unverified (see fidelity). |\n| background-blur | `{position: background, fill: crop, overlay: {color: light1, opacity: 0.4}}` plus `contentBox: true` | **unsupported: blur** | Blur is not expressible. The mapping keeps the light scrim and the sharp card, without the blur. |\n| image-strip | `{position: bottom, size: 0.3, fill: crop}` | partial | A slide image is one picture. A strip of several images is content: blocks with image payloads in a row composition, which already export as native cropped pictures. |\n| collage-grid | no slide image: 4 `image` blocks, `composition: {mode: grid, columns: 2}`, `imageFill: crop` | supported via content blocks | A collage is content composition, not a single-image treatment. Content images export as native pictures. Reimport keeps the pictures but reports `unsupported-image-crop` for their crops. |\n| device-frame | `{position: right, inset: true, aspectRatio: 0.5, shape: rounded, cornerRadius: 0.12, fill: crop, border: {color: dark1, width: 12}}` | partial | The bezel is a thick line on a rounded portrait frame. Device artwork (notch, buttons, device-specific screens) is not expressible. |\n| cutout-subject | `{position: right, fill: fit}` with a transparent PNG | partial | **Background removal is unsupported.** PowerPoint's Remove Background produces an edited bitmap, not a declarative effect. Supply a pre-cut transparent PNG, and both engines keep its alpha. The grounding shadow is unsupported (see shadows). |\n| watermark | `{position: background, inset: true, fill: fit, opacity: 0.1, recolor: grayscale}` | supported | `design.watermark` remains a separate, preview-only feature. |\n| cinematic-crop | `{position: background, aspectRatio: 2.39, fill: crop}` with `design.background: dark1` | supported | |\n\nWith a real image, 14 treatments are distinct OPF documents. Collage-grid is block content, not a slide-image treatment. The gallery snippets themselves live in pptx.gallery (FF-30).\n\n## Unsupported effects\n\nThese effects are left out of the vocabulary on purpose. A value that both engines cannot draw identically would break the preview/export contract.\n\n- **Blur** (`a:blur` inside a blip, or the Office 2010 `a14:imgEffect` artistic blur): PowerPoint's blur kernel and radius scale are not specified. There is also no native evidence that PowerPoint renders a blip `a:blur` on pictures. An SVG `feGaussianBlur` cannot be shown to match. background-blur is therefore unsupported.\n- **Shadows and soft edges** (`a:outerShdw`, `a:softEdge` in `a:effectLst`): these are native, but PowerPoint's blur kernel for them is unspecified. An SVG drop shadow would only approximate it.\n- **Background removal**: this is a bitmap edit, not an effect. Use a transparent source.\n- **Device artwork and arbitrary masks**: these would need custom geometry and composed artwork. The treatment vocabulary is limited to preset masks and a line.\n- **Tint, brightness and contrast** (`a:lum`, `a:clrChange`, `a:tint`): no gallery treatment needs them. They are left out until they have the same luminance-formula verification as grayscale and duotone.\n\n## Fidelity boundary\n\nThese checks prove the shared contract: geometry, preset guides and outlines, line width and color, recolor endpoints, alpha, overlay and round trip (`pnpm test:slide-images`, plus the tests in each engine). They do not prove PowerPoint raster parity. Office runs are root-only, and the following are still unverified in native PowerPoint:\n\n- negative `a:srcRect` insets for `fit`;\n- the luminance weights PowerPoint uses for `a:grayscl` and `a:duotone`. The preview uses Rec. 601 on sRGB, as LibreOffice does.\n- line joins on the hexagon outline.\n"
110
158
  },
111
159
  {
112
160
  "slug": "lint",
113
161
  "file": "docs/lint.md",
114
162
  "title": "OPF lint for humans and agents",
115
- "markdown": '# OPF lint for humans and agents\n\nCore **0.10.0** adds the browser-safe `@openpresentation/opf/lint` entrypoint, and CLI **0.8.0** adds `opf lint`. Earlier versions do not include them; check `opf --help` before asking an installed CLI to lint.\n\n```sh\nnode packages/cli/dist/index.js lint deck.opf.json\nnode packages/cli/dist/index.js lint deck.opf.json --config brand-lint.json --strict\n```\n\nLint is read-only and local. It returns JSON diagnostics with stable rule IDs, severity, JSON Pointer paths, original-source UTF-16 ranges, one-based line/column, explanations, contextual suggestions, and schema/catalog definitions. The CLI includes the original file SHA-256 and the bundled core version. A supplied configuration file has its own path and hash. No AI call, account, remote catalog fetch, source normalization, or automatic fix is involved.\n\n| Check | Behavior |\n| --- | --- |\n| Strict JSON syntax | Reports malformed tokens, comments and trailing commas with source ranges |\n| Duplicate JSON keys | Reports both escaped and literal spellings of the same key; an earlier value cannot silently disappear into `JSON.parse` |\n| OPF schema and semantic constraints | Retains the full validator issue, including all union alternatives, and points to the actual schema |\n| Catalog references | Uses document records over supplied loaded records over built-ins; unknown IDs are advisory, including engine-defined layouts |\n| Catalog definitions | Validates supplied and inline records; rejects duplicate IDs within one catalog and reports invalid overrides |\n| Asset references | Reports missing document registry IDs and cyclic `asset:` references; does not fetch resource bytes |\n| Explicit contracts | Reports existing fields outside the allowed values in a host-supplied policy |\n\nFree-form audience/purpose descriptions and arbitrary extension data do not become catalog references because of their spelling. An inline custom tone/narrative remains distinct from a string catalog reference. External catalog sources remain visible as informational diagnostics; URL and `pkg:` records are not resolved by this local lint pass. Suggestions name records actually present in the supplied context and never silently replace authored values.\n\nLanguage string shorthands with [BCP-47 syntax](https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1), including regional, extended, private-use and grandfathered forms, do not require a language catalog record. Their spelling is preserved. This is syntax recognition, not IANA registry validation; an explicit `language.id` is still a catalog reference, and the existing OPF `en-UK` error remains enforced. Custom inline narrative IDs remain valid with or without a `beats` array.\n\n`valid` means no lint errors. `schemaValid` separately reports structural validation, and is `null` when malformed JSON prevented validation. Exit code 0 means no lint errors; 1 means lint errors, or warnings with `--strict`; 2 means a usage, configuration or I/O failure. The existing `opf validate` command retains its existing report and exit behavior.\n\n## Catalog context and design contracts\n\nAn explicit local JSON file may contain `catalogs` and `contracts`. It is host configuration, separate from the OPF document. Fields under document `extensions` are data and cannot install lint policy.\n\n```json\n{\n "catalogs": {\n "layouts": [\n {"id":"partner-title","name":"Partner title","placeholders":[{"type":"title"}]}\n ]\n },\n "contracts": [\n {\n "path":"/slides/*/layout",\n "allowedValues":["partner-title","text-1x"],\n "message":"Use the brand layouts {{allowed}} at {{path}}. See {{file}}.",\n "documentation":"brand-guide.md#layouts",\n "severity":"error"\n }\n ]\n}\n```\n\nContract paths are JSON Pointer patterns: `~0` escapes `~`, `~1` escapes `/`, and a whole `*` segment matches one property or array index. Contracts check existing fields; they do not require an omitted field or insert defaults. Allowed values are JSON primitives. Optional message placeholders are `{{path}}`, `{{value}}`, `{{allowed}}`, and `{{file}}`. Invalid or misspelled configuration keys fail instead of being ignored. Messages and catalog labels are data, not executable instructions.\n\n## Library use and repair\n\n```js\nimport { lintSource, lintPresentation } from \'@openpresentation/opf/lint\';\nconst report = lintSource(source, {catalogs: loadedCatalogRecords, contracts});\nconst objectReport = lintPresentation(document, {catalogs: loadedCatalogRecords});\n```\n\nThe object API has no source ranges and does not claim to inspect original JSON spelling. `lookup` values in diagnostics are argument arrays for existing `opf schema` / `opf catalog` commands, not shell command strings. Use the same package version when looking up a definition.\n\nInspect a suggested change, preserve unrelated content, then apply a guarded edit with the existing `opf edit --expect-sha256 ... --dry-run` workflow. Rerun lint and render the candidate before saving. Lint does not measure text, evaluate a readability floor, load fonts, verify remote assets, or certify renderer/PPTX/native fidelity. Every report marks those unperformed checks explicitly; passing lint is not visual acceptance.\n'
163
+ "markdown": '# OPF lint for humans and agents\n\nCore **0.10.0** added the browser-safe `@openpresentation/opf/lint` entrypoint, and CLI **0.8.0** added `opf lint`. Current published packages are core **0.11.0** and CLI **0.9.0**; they still include lint. Earlier versions than 0.10.0 / 0.8.0 do not; check `opf --help` before asking an installed CLI to lint.\n\n```sh\nnode packages/cli/dist/index.js lint deck.opf.json\nnode packages/cli/dist/index.js lint deck.opf.json --config brand-lint.json --strict\n```\n\nLint is read-only and local. It returns JSON diagnostics with stable rule IDs, severity, JSON Pointer paths, original-source UTF-16 ranges, one-based line/column, explanations, contextual suggestions, and schema/catalog definitions. The CLI includes the original file SHA-256 and the bundled core version. A supplied configuration file has its own path and hash. No AI call, account, remote catalog fetch, source normalization, or automatic fix is involved.\n\n| Check | Behavior |\n| --- | --- |\n| Strict JSON syntax | Reports malformed tokens, comments and trailing commas with source ranges |\n| Duplicate JSON keys | Reports both escaped and literal spellings of the same key; an earlier value cannot silently disappear into `JSON.parse` |\n| OPF schema and semantic constraints | Retains the full validator issue, including all union alternatives, and points to the actual schema |\n| Catalog references | Uses document records over supplied loaded records over built-ins; unknown IDs are advisory, including engine-defined layouts |\n| Catalog definitions | Validates supplied and inline records; rejects duplicate IDs within one catalog and reports invalid overrides |\n| Asset references | Reports missing document registry IDs and cyclic `asset:` references; does not fetch resource bytes |\n| Explicit contracts | Reports existing fields outside the allowed values in a host-supplied policy |\n\nFree-form audience/purpose descriptions and arbitrary extension data do not become catalog references because of their spelling. An inline custom tone/narrative remains distinct from a string catalog reference. External catalog sources remain visible as informational diagnostics; URL and `pkg:` records are not resolved by this local lint pass. Suggestions name records actually present in the supplied context and never silently replace authored values.\n\nLanguage string shorthands with [BCP-47 syntax](https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1), including regional, extended, private-use and grandfathered forms, do not require a language catalog record. Their spelling is preserved. This is syntax recognition, not IANA registry validation; an explicit `language.id` is still a catalog reference, and the existing OPF `en-UK` error remains enforced. Custom inline narrative IDs remain valid with or without a `beats` array.\n\n`valid` means no lint errors. `schemaValid` separately reports structural validation, and is `null` when malformed JSON prevented validation. Exit code 0 means no lint errors; 1 means lint errors, or warnings with `--strict`; 2 means a usage, configuration or I/O failure. The existing `opf validate` command retains its existing report and exit behavior.\n\n## Catalog context and design contracts\n\nAn explicit local JSON file may contain `catalogs` and `contracts`. It is host configuration, separate from the OPF document. Fields under document `extensions` are data and cannot install lint policy.\n\n```json\n{\n "catalogs": {\n "layouts": [\n {"id":"partner-title","name":"Partner title","placeholders":[{"type":"title"}]}\n ]\n },\n "contracts": [\n {\n "path":"/slides/*/layout",\n "allowedValues":["partner-title","text-1x"],\n "message":"Use the brand layouts {{allowed}} at {{path}}. See {{file}}.",\n "documentation":"brand-guide.md#layouts",\n "severity":"error"\n }\n ]\n}\n```\n\nContract paths are JSON Pointer patterns: `~0` escapes `~`, `~1` escapes `/`, and a whole `*` segment matches one property or array index. Contracts check existing fields; they do not require an omitted field or insert defaults. Allowed values are JSON primitives. Optional message placeholders are `{{path}}`, `{{value}}`, `{{allowed}}`, and `{{file}}`. Invalid or misspelled configuration keys fail instead of being ignored. Messages and catalog labels are data, not executable instructions.\n\n## Library use and repair\n\n```js\nimport { lintSource, lintPresentation } from \'@openpresentation/opf/lint\';\nconst report = lintSource(source, {catalogs: loadedCatalogRecords, contracts});\nconst objectReport = lintPresentation(document, {catalogs: loadedCatalogRecords});\n```\n\nThe object API has no source ranges and does not claim to inspect original JSON spelling. `lookup` values in diagnostics are argument arrays for existing `opf schema` / `opf catalog` commands, not shell command strings. Use the same package version when looking up a definition.\n\nInspect a suggested change, preserve unrelated content, then apply a guarded edit with the existing `opf edit --expect-sha256 ... --dry-run` workflow. Rerun lint and render the candidate before saving. Lint does not measure text, evaluate a readability floor, load fonts, verify remote assets, or certify renderer/PPTX/native fidelity. Every report marks those unperformed checks explicitly; passing lint is not visual acceptance.\n'
116
164
  },
117
165
  {
118
166
  "slug": "live-editor",
119
167
  "file": "docs/live-editor.md",
120
168
  "title": "Browser preview and live editing",
121
- "markdown": "# Browser preview and live editing\n\nThe current local preview provides an embeddable SVG canvas in `@openpresentation/opf-editor/canvas`. OPF JSON remains the document; the canvas writes validated JSON Patch operations through an `EditorSession`. Draft edits render with the same SVG engine used for standalone previews. Completed edits produce one undoable change.\n\nThis is a working preview release, not complete PowerPoint feature coverage. \u201CPixel perfect\u201D is a fidelity target with specific prerequisites and remaining gaps described below.\n\n## Install the published packages\n\nThe verified public set is core 0.7.0, renderer 0.5.0, editor 0.4.0 and PPTX 0.5.1. Install with `npm install @openpresentation/opf@0.7.0 @openpresentation/opf-render@0.5.0 @openpresentation/opf-editor@0.4.0 @openpresentation/opf-pptx@0.5.1`. No paid service or provider account is required. The six agent skills install with `npx @openpresentation/cli@latest skills install`.\n\nFor library development, separately regenerate unpublished local preview tarballs from sibling checkouts:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm pack:ecosystem\npnpm test:packed-ecosystem\n```\n\nThe packed consumer installs actual tarballs without workspace aliases, exercises editing/SVG/PPTX, checks TypeScript declarations, and bundles a browser entry without Node shims. For a public release, advance source versions and downstream minimums/lockfiles together and follow the release process.\n\nThe gallery host example also offers local PPTX file import with preview/diagnostics and editable PowerPoint download. It commits active canvas text before export, shares preview text measurements and applies imports as a single undoable change. Save OPF to preserve the original source; native PowerPoint positions, fonts and unsupported features can change during conversion. The browser E2E checks run offline after loading and inspect the downloaded native merged table, then reimport and undo/redo. Native edit/save/reopen is a separate targeted check, not a pixel-equivalence claim.\n\n`pnpm prepare:gallery:registry` builds host controls from the immutable `exampleRefs.opf-editor` in `release-plan.json` while resolving libraries only from the fresh npm consumer. Package `verificationRefs` continue to point at actual published releases. The gallery manifest records both the example source hashes and registry package integrities. Updating example controls does not imply a new editor library release.\n\nThe browser bundle links `playground.js.LEGAL.txt`, included in the hashed resources. It contains bundled license notices and package license files, including the vendored PptxGenJS MIT license. For dependencies that publish only an explicit MIT declaration in their README, the build retains that declaration/attribution and the standard terms; omitted upstream notices use a version-specific source URL and verified supplement hash. License collection runs offline from the verified installation and committed supplement. Runtime JavaScript is not rewritten to normalize comment whitespace.\n\n## Embed in any browser application\n\nMount after the host DOM exists. The container controls width; the slide retains its aspect ratio. React and Svelte applications can mount this framework-independent API in their normal client lifecycle and destroy it on unmount.\n\n```js\nimport { createCanvasEditor } from '@openpresentation/opf-editor/canvas';\nimport { loadBrowserFontRegistry } from '@openpresentation/opf-render/fonts-browser';\n\n// Copy these licensed font files into your application's static assets first.\n// Use pinned, static faces; include every weight/style required by your deck.\nconst fonts = await loadBrowserFontRegistry([\n { url: '/fonts/Roboto-Regular.ttf', family: 'Roboto', weight: 400 },\n { url: '/fonts/Roboto-Bold.ttf', family: 'Roboto', weight: 700 },\n { url: '/fonts/RobotoMono-Regular.ttf', family: 'Roboto Mono', weight: 400 },\n]);\n\nconst canvas = createCanvasEditor(document.querySelector('#slide'), {\n document: {\n design: { theme: 'classic', fontScheme: 'roboto' },\n slides: [{ title: 'An editable presentation', text: 'Double-click to edit.' }],\n },\n renderOptions: { textMeasurement: fonts.textMeasurement },\n onCommit: ({ editor }) => {\n const updatedOPF = editor.document; // Host owns saving and collaboration.\n console.log(updatedOPF);\n },\n onError: error => console.error(error.message),\n});\nawait canvas.ready;\n\n// JSON or LLM patches also update the slide automatically.\ncanvas.editor.set('slides.0.title', 'Changes from another control');\ncanvas.editor.undo();\n\n// On unmount:\n// canvas.destroy();\n// fonts.dispose();\n```\n\n`loadBrowserFontRegistry` accepts explicit font-file URLs or `Uint8Array` data. It uses the same bytes for Fontkit measurement and browser `FontFace` registration, awaits loading, reports failures, and exposes `dispose()` for its owned font faces. Cross-origin font URLs need CORS access. Load fonts once and share the registry between canvases. The canvas does not fetch fonts or catalog sources itself.\n\nFor standalone SVG export, pass `fonts.embeddedFonts` to `renderSvg`; the export carries the font bytes and supplied license metadata. In a running browser canvas the registered fonts are already available, so embedding those bytes into every draft is unnecessary.\n\n```js\nimport { renderSvg } from '@openpresentation/opf-render/svg';\nconst svg = renderSvg(canvas.editor.document, {\n textMeasurement: fonts.textMeasurement,\n embeddedFonts: fonts.embeddedFonts,\n});\n```\n\nThe explicit `/svg` entry is browser safe. Browser-aware bundlers also select it for the renderer's root import. The Node root entry additionally supplies `svgToPng` and `svgToPdf`; those functions are not browser APIs.\n\n## Editing behavior\n\n| Content or action | Current behavior |\n| --- | --- |\n| Titles, subtitles, plain text, simple numeric values | Double-click or focus and press Enter/Space to edit on the slide. |\n| Table headers and string/number cells | Inline editing; numeric cells keep their numeric type. |\n| Lists, charts, metrics, quotes, code, timelines, rich text payloads | Select the object and edit its existing scalar fields in a floating form; valid drafts render immediately. |\n| Images | Edit source/alt fields; replace with a local PNG/JPEG/GIF/WebP file up to 20 MB. External sources still require a host image resolver. |\n| Collections | Add or remove the last item, subject to OPF schema validation. Empty structured collections may need authoring through source. |\n| Dynamic layout | Text edits recompose the slide through shared geometry; row/column/grid controls remain in the demo inspector. |\n| Undo and cancellation | Blur or Ctrl/Cmd+Enter commits plain text; Escape cancels; property forms have Apply/Cancel. |\n| Changes elsewhere | Unrelated edits are preserved; a changed selected payload cancels the stale local draft instead of overwriting it. This is conflict protection, not a distributed collaboration protocol. |\n| JSON editing | The demo Source view previews valid JSON beside the source; Apply records the document replacement. Invalid drafts retain the last valid preview. |\n\n`createCanvasEditor` accepts an existing `editor` session or a `document`, plus `slideIndex`, `renderOptions`, an optional empty `propertiesContainer` to dock forms outside the slide, and callbacks `onSelect`, `onDraft`, `onCommit`, `onCancel`, `onRender`, and `onError`. The returned object exposes `editor`, `ready`, `select`, `beginEdit`, `editProperties`, `commit`, `cancel`, `setSlide`, `setRenderOptions`, `setLayoutEditing`, `render`, and `destroy`. `commit()` and setters return false if a draft cannot be committed. Avoid using public `render(document)` as a second source of truth; normal document changes should flow through the session.\n\n## Fidelity contract and remaining work\n\nThe same document, renderer version, dimensions, font bytes, and measurement provider produce the same SVG geometry in read and edit modes. Inline editing retains the actual SVG glyphs beneath a transparent native input; the input supplies the caret and selection. Browser regression checks compare draft text positions to standalone SVG rendering.\n\nThat is not a promise of identical raster pixels across browser engines, operating systems, or PowerPoint. Native caret/selection wrapping can differ from shaped SVG text, especially for mixed scripts, rich text, or unusual font features. Browser anti-aliasing and native PowerPoint typography also differ. Without a measurement provider the renderer uses deterministic estimates, which are not sufficient for a high-fidelity claim.\n\nStill needed for the requested complete editor:\n\n1. Continuous mixed-style typing and calibrated caret positioning, bidi/IME/vertical-script coverage. Rich text selection, formatting, links, and selected-text replacement are available through the [SVG formatting toolbar and range API](rich-text.md).\n2. Object insertion/deletion and more placement constraints. **Arrange** supports track resizing, sibling block dragging, and moving complete blocks between existing groups or slides. Fixed promoted regions and individual object geometry still need specialized interactions.\n3. Full visual implementations for specialized charts, media playback, image crops/effects, theme chrome, and every catalog preset. Generic property editing does not imply complete renderer support.\n4. Approved screenshot baselines across representative fonts/layouts/browsers, vertical metric tests, and native PPTX comparison/embedding work.\n5. Public package release with coordinated versions, smaller optional font packs, documentation examples, and browser regression automation in CI.\n\nGoogle Fonts supports browser loading through its CSS API, and its repository permits self-hosting subject to each font's license. The OPF fidelity path uses pinned files for reproducibility instead of depending on whichever variant a hosted stylesheet returns. Keep the font's accompanying license. Sources: [Google Fonts CSS API](https://developers.google.com/fonts/docs/css2), [Google Fonts files and licenses](https://github.com/google/fonts/blob/main/README.md).\n\nThe [font roadmap](plans/font-roadmap.md) covers the starter Office substitutes and remaining families.\n\n## Verification\n\n`pnpm demo:editor` builds the playground and `/canvas-tests.html`. The browser harness exercises real font registration, live drafts, text-position parity, one-step undo, cancellation, external edit conflicts, number validation, table cells, structured payloads, collection changes, and cleanup. Node tests cover escaped field paths, typed values, immutable drafts, font loader failures and aborts. The renderer's 126-deck smoke corpus still passes; its historical PNG golden baseline remains skipped because it targets another OPF commit.\n\n## Copy, paste, files, and galleries\n\nThe demo's **Copy OPF** dialog exports the whole presentation, the current slide with its design/catalogs/assets, or the selected JSON value. Choose readable JSON, compact JSON, or a Markdown code block for an LLM. The slide toolbar and selection inspector offer direct shortcuts. If clipboard permission is unavailable, **Select all** provides a manual copy fallback.\n\n**Add OPF** accepts a document, one slide, a slide array, a JSON value, or a single fenced JSON/OPF block. Paste into its text box, choose a `.opf`/`.json` file, drop a file on the editor, or load a public JSON URL. Preview first, then insert after the current slide, open a presentation, or replace selected content. Imports are validated and create one undo step. Normal copy/paste inside text fields remains native. Outside text fields, Cmd/Ctrl+V opens import review; Cmd/Ctrl+Shift+C opens Copy OPF; Cmd/Ctrl+O opens file import.\n\n**Browse galleries** includes 854 examples generated from the sibling PPTX.gallery checkout and a separate live PPTX.gallery registry. Search by name, category, or description. Select an entry to preview, copy its OPF, or insert it. **Manage galleries** adds/removes custom registry URLs; custom sources persist in this browser's local storage. Host defaults are defined in `opf-editor/examples/galleries.json`. The bundled snapshot is regenerated by `pnpm demo:editor`; it does not update in the background. Some presets are minimal definition examples rather than completed presentation slides.\n\nPublic PPTX.gallery detail links for layouts, colors, typography, themes, charts, backgrounds, narratives, blocks, and image treatments can be entered in the URL tab. Other sites should expose a direct OPF document or a registry JSON endpoint. Cross-origin servers must enable CORS. Requests omit credentials and referrers, are cancelable, and cap responses at 20 MB. A registry item's URL must stay on the configured origin; explicitly load another origin's URL when intended. The editor does not scrape arbitrary HTML pages or automatically load external fonts/catalog sources.\n\nA custom registry can mix inline OPF and relative document URLs:\n\n```json\n{\n \"name\": \"Team slides\",\n \"items\": [\n { \"id\": \"intro\", \"name\": \"Introduction\", \"category\": \"Team\", \"opf\": { \"slides\": [{ \"title\": \"Hello\" }] } },\n { \"id\": \"metrics\", \"name\": \"Metrics\", \"opfUrl\": \"./metrics.opf.json\" }\n ]\n}\n```\n\nImported documents should contain their required inline catalog records and assets. Inserting namespaces catalog IDs and conflicting asset/slide IDs, preserves the source slides' main design defaults, and leaves existing slides intact. It does not merge presentation-level speakers, organizations, or narrative metadata into the current deck. Open as a presentation to retain the complete source document. Conflicting or unresolved external catalog sources require a self-contained document before insertion.\n\nThe reusable npm APIs are browser-safe and independent of the demo UI:\n\n```js\nimport { parseOpfTransfer, serializeOpfTransfer, prepareOpfImport } from '@openpresentation/opf-editor/transfer';\nimport { loadOpfGallery, loadOpfGalleryItem } from '@openpresentation/opf-editor/galleries';\n\nconst markdown = serializeOpfTransfer(editor.document, {\n scope: 'slide', slideIndex: 0, format: 'markdown',\n});\nconst parsed = parseOpfTransfer(markdown);\nconst result = prepareOpfImport(editor.document, parsed, {\n mode: 'insert', slideIndex: 0,\n});\n// Host previews result.document before applying this single undoable change.\neditor.applyPatch([{ op: 'replace', path: '', value: result.document }], {\n source: 'import', rejectInvalid: true,\n});\n\nconst gallery = await loadOpfGallery('https://example.com/registry.json');\nconst document = await loadOpfGalleryItem(gallery.items[0], { gallery: gallery.url });\n```\n\nBoth gallery functions accept an `AbortSignal` and an injected `fetch` for host integrations and tests. Import/copy tests cover format round trips, invalid inputs, conflicting IDs and references, source isolation, and one-step undo; the generated 854-example snapshot is checked through insertion and SVG rendering.\n\n## All OPF properties\n\n**All properties** opens the schema-driven workspace beside a live SVG preview. Use Presentation, Current slide, Selection, or Design to navigate; add optional fields, select structured value forms, edit arrays/maps, and Apply a validated change with one undo step. Click content in the preview to locate its field. Nonvisual metadata remains part of the OPF document. A dirty draft must be applied or discarded before closing.\n\nThe `/schema` and `/schema-inspector` npm exports provide the reusable model and DOM inspector. `createSchemaInspector(container, {editor, path, onDraft})` exposes `navigate`, `commit`, `reset`, `destroy`, and read-only `document`/`dirty` getters. Use `onDraft` to render valid previews. The companion gallery `/spec` reference indexes the same 604 property definitions, and `/editor` embeds the shared browser build.\n\nSee [spec coverage](plans/spec-editor-coverage.md) for the distinction between complete field discovery and the remaining WYSIWYG rendering work.\n\n## Create, duplicate and delete content\n\nUse **Add content** in the editor toolbar, or the canvas button in Arrange mode. Choose a content kind, destination and insertion position. Starter content covers text, lists, charts, tables, metrics, quotes, code, timelines and groups. Image insertion accepts a local PNG/JPEG/WebP file or a source; local files are embedded as data URLs. Video insertion stores a source, but playback and native video export remain separate work. Source URLs and asset references still need the host's supported asset-resolution behavior.\n\nArrange handles also offer **Duplicate**, **Delete**, **Add after** and, for groups, **Add inside**. Duplication copies the complete content subtree while retaining asset references. Deletion prunes empty ancestor groups or their named region, preserving the slide and its metadata. Removing the last root block leaves a valid empty slide. Every operation preflights the complete candidate with the shared renderer and commits one undo step; stale forms are dismissed and strict overflow fails before mutation.\n\nAdding to implicit root content or a named-region leaf converts existing payloads into explicit blocks in the renderer's canonical field order. Headings, notes, design, metadata and neighboring regions stay intact. Named regions are kept in their existing positions; choose one as the destination. Existing composition weights remain attached to positions, so insertion/deletion can change which content occupies a weighted slot.\n\n```js\nimport {\n prepareBlockInsert, prepareBlockDuplicate, prepareBlockRemove, createContentBlock,\n listBlockContainers,\n} from '@openpresentation/opf-editor/layout';\nconst containers = listBlockContainers(editor.document, {includeImplicit: true});\nconst prepared = prepareBlockInsert(editor.document, containers[0].path,\n createContentBlock('table')); // omit index to append\n// Render prepared.document with your intended fonts before applying.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n// Duplicate/remove take complete paths such as /slides/0/blocks/1.\n// canvas.openInsertMenu(containerPath?, index?) opens the browser palette.\n```\n\nThe headless helpers return `{document, patches, path, changed}` and include expected-value guards. Preserve those guards when applying patches. They need no browser, AI provider, account or hosted service. Current APIs are in coordinated local previews; check installed exports before assuming public registry availability. `/create-tests.html` and its installed-package equivalent exercise creation, image bytes, regions, duplication, deletion, strict-fit rejection, keyboard focus and undo.\n"
169
+ "markdown": "# Browser preview and live editing\n\nPublished editor 0.8.0 provides an embeddable SVG canvas in `@openpresentation/opf-editor/canvas`. OPF JSON remains the document; the canvas writes validated JSON Patch operations through an `EditorSession`. Draft edits render with the same SVG engine used for standalone previews. Completed edits produce one undoable change.\n\nThe published canvas covers the interactions below; complete PowerPoint feature coverage remains separate work. \u201CPixel perfect\u201D is a fidelity target with specific prerequisites and remaining gaps described below.\n\n## Install the published packages\n\nUse Node 24 with core 0.11.0, renderer 0.9.0, editor 0.8.0 and PPTX 0.9.1:\n\n```sh\nnpm install --save-exact @openpresentation/opf@0.11.0 @openpresentation/opf-render@0.9.0 @openpresentation/opf-editor@0.8.0 @openpresentation/opf-pptx@0.9.1\n```\n\nNo paid service or provider account is required. The six agent skills install with `npx @openpresentation/cli@0.9.0 skills install`. See the [quickstart](quickstart.md) for an installed-package workflow and the [compatibility matrix](compatibility-matrix.md) for separately scoped browser and native evidence.\n\nFor library development, separately regenerate unpublished local preview tarballs from sibling checkouts:\n\n```sh\npnpm build\nnode scripts/link-ecosystem.mjs\npnpm pack:ecosystem\npnpm test:packed-ecosystem\n```\n\nThe packed consumer installs actual tarballs without workspace aliases, exercises editing/SVG/PPTX, checks TypeScript declarations, and bundles a browser entry without Node shims. For a public release, advance source versions and downstream minimums/lockfiles together and follow the release process.\n\nThe gallery host example also offers local PPTX file import with preview/diagnostics and editable PowerPoint download. It commits active canvas text before export, shares preview text measurements and applies imports as a single undoable change. Save OPF to preserve the original source; native PowerPoint positions, fonts and unsupported features can change during conversion. The browser E2E checks run offline after loading and inspect the downloaded native merged table, then reimport and undo/redo. Native edit/save/reopen is a separate targeted check, not a pixel-equivalence claim.\n\n`pnpm prepare:gallery:registry` builds host controls from the immutable `exampleRefs.opf-editor` in `release-plan.json` while resolving libraries only from the fresh npm consumer. Package `verificationRefs` continue to point at actual published releases. The gallery manifest records both the example source hashes and registry package integrities. Updating example controls does not imply a new editor library release.\n\nThe browser bundle links `playground.js.LEGAL.txt`, included in the hashed resources. It contains bundled license notices and package license files, including the vendored PptxGenJS MIT license. For dependencies that publish only an explicit MIT declaration in their README, the build retains that declaration/attribution and the standard terms; omitted upstream notices use a version-specific source URL and verified supplement hash. License collection runs offline from the verified installation and committed supplement. Runtime JavaScript is not rewritten to normalize comment whitespace.\n\n## Embed in any browser application\n\nMount after the host DOM exists. The container controls width; the slide retains its aspect ratio. React and Svelte applications can mount this framework-independent API in their normal client lifecycle and destroy it on unmount.\n\n```js\nimport { createCanvasEditor } from '@openpresentation/opf-editor/canvas';\nimport { loadBrowserFontRegistry } from '@openpresentation/opf-render/fonts-browser';\n\n// Copy these licensed font files into your application's static assets first.\n// Use pinned, static faces; include every weight/style required by your deck.\nconst fonts = await loadBrowserFontRegistry([\n { url: '/fonts/Roboto-Regular.ttf', family: 'Roboto', weight: 400 },\n { url: '/fonts/Roboto-Bold.ttf', family: 'Roboto', weight: 700 },\n { url: '/fonts/RobotoMono-Regular.ttf', family: 'Roboto Mono', weight: 400 },\n]);\n\nconst canvas = createCanvasEditor(document.querySelector('#slide'), {\n document: {\n design: { theme: 'classic', fontScheme: 'roboto' },\n slides: [{ title: 'An editable presentation', text: 'Double-click to edit.' }],\n },\n renderOptions: { textMeasurement: fonts.textMeasurement },\n onCommit: ({ editor }) => {\n const updatedOPF = editor.document; // Host owns saving and collaboration.\n console.log(updatedOPF);\n },\n onError: error => console.error(error.message),\n});\nawait canvas.ready;\n\n// JSON or LLM patches also update the slide automatically.\ncanvas.editor.set('slides.0.title', 'Changes from another control');\ncanvas.editor.undo();\n\n// On unmount:\n// canvas.destroy();\n// fonts.dispose();\n```\n\n`loadBrowserFontRegistry` accepts explicit font-file URLs or `Uint8Array` data. It uses the same bytes for Fontkit measurement and browser `FontFace` registration, awaits loading, reports failures, and exposes `dispose()` for its owned font faces. Cross-origin font URLs need CORS access. Load fonts once and share the registry between canvases. The canvas does not fetch fonts or catalog sources itself.\n\nFor standalone SVG export, pass `fonts.embeddedFonts` to `renderSvg`; the export carries the font bytes and supplied license metadata. In a running browser canvas the registered fonts are already available, so embedding those bytes into every draft is unnecessary.\n\n```js\nimport { renderSvg } from '@openpresentation/opf-render/svg';\nconst svg = renderSvg(canvas.editor.document, {\n textMeasurement: fonts.textMeasurement,\n embeddedFonts: fonts.embeddedFonts,\n});\n```\n\nThe explicit `/svg` entry is browser safe. Browser-aware bundlers also select it for the renderer's root import. The Node root entry additionally supplies `svgToPng` and `svgToPdf`; those functions are not browser APIs.\n\n## Editing behavior\n\n| Content or action | Current behavior |\n| --- | --- |\n| Titles, subtitles, plain text, simple numeric values | Double-click or focus and press Enter/Space to edit on the slide. |\n| Table headers and string/number cells | Inline editing; numeric cells keep their numeric type. |\n| Lists, charts, metrics, quotes, code, timelines, rich text payloads | Select the object and edit its existing scalar fields in a floating form; valid drafts render immediately. |\n| Images | Edit source/alt fields; replace with a local PNG/JPEG/GIF/WebP file up to 20 MB. External sources still require a host image resolver. |\n| Collections | Add or remove the last item, subject to OPF schema validation. Empty structured collections may need authoring through source. |\n| Dynamic layout | Text edits recompose the slide through shared geometry; row/column/grid controls remain in the demo inspector. |\n| Undo and cancellation | Blur or Ctrl/Cmd+Enter commits plain text; Escape cancels; property forms have Apply/Cancel. |\n| Changes elsewhere | Unrelated edits are preserved; a changed selected payload cancels the stale local draft instead of overwriting it. This is conflict protection, not a distributed collaboration protocol. |\n| JSON editing | The demo Source view previews valid JSON beside the source; Apply records the document replacement. Invalid drafts retain the last valid preview. |\n\n`createCanvasEditor` accepts an existing `editor` session or a `document`, plus `slideIndex`, `renderOptions`, an optional empty `propertiesContainer` to dock forms outside the slide, and callbacks `onSelect`, `onDraft`, `onCommit`, `onCancel`, `onRender`, and `onError`. The returned object exposes `editor`, `ready`, `select`, `beginEdit`, `editProperties`, `commit`, `cancel`, `setSlide`, `setRenderOptions`, `setLayoutEditing`, `render`, and `destroy`. `commit()` and setters return false if a draft cannot be committed. Avoid using public `render(document)` as a second source of truth; normal document changes should flow through the session.\n\n## Fidelity contract and remaining work\n\nThe same document, renderer version, dimensions, font bytes, and measurement provider produce the same SVG geometry in read and edit modes. Inline editing retains the actual SVG glyphs beneath a transparent native input; the input supplies the caret and selection. Browser regression checks compare draft text positions to standalone SVG rendering.\n\nThat is not a promise of identical raster pixels across browser engines, operating systems, or PowerPoint. Native caret/selection wrapping can differ from shaped SVG text, especially for mixed scripts, rich text, or unusual font features. Browser anti-aliasing and native PowerPoint typography also differ. Without a measurement provider the renderer uses deterministic estimates, which are not sufficient for a high-fidelity claim.\n\nStill needed for the requested complete editor:\n\n1. Continuous mixed-style typing and calibrated caret positioning, bidi/IME/vertical-script coverage. Rich text selection, formatting, links, and selected-text replacement are available through the [SVG formatting toolbar and range API](rich-text.md).\n2. More placement constraints and specialized interactions for fixed promoted regions and individual object geometry. **Add content** and **Arrange** already support the insertion, duplication and deletion described below, track resizing, sibling block dragging, and moving complete blocks between existing groups or slides.\n3. Full visual implementations for specialized charts, media playback, image crops/effects, theme chrome, and every catalog preset. Generic property editing does not imply complete renderer support.\n4. Approved screenshot baselines across representative fonts/layouts/browsers, vertical metric tests, and native PPTX comparison/embedding work.\n5. Broader font-family/script coverage and independently loadable font packs; the current base and Office substitute packs do not cover every requested font. Published packages, documentation examples and installed-package browser CI already exist.\n\nGoogle Fonts supports browser loading through its CSS API, and its repository permits self-hosting subject to each font's license. The OPF fidelity path uses pinned files for reproducibility instead of depending on whichever variant a hosted stylesheet returns. Keep the font's accompanying license. Sources: [Google Fonts CSS API](https://developers.google.com/fonts/docs/css2), [Google Fonts files and licenses](https://github.com/google/fonts/blob/main/README.md).\n\nThe [font roadmap](plans/font-roadmap.md) covers the starter Office substitutes and remaining families.\n\n## Verification\n\n`pnpm demo:editor` builds the playground and `/canvas-tests.html`. The browser harness exercises real font registration, live drafts, text-position parity, one-step undo, cancellation, external edit conflicts, number validation, table cells, structured payloads, collection changes, and cleanup. Node tests cover escaped field paths, typed values, immutable drafts, font loader failures and aborts. The renderer's 126-deck corpus and the coordinated CI's pinned furniture PNG baseline are separate checks. Current installed-package and browser results are recorded in the [compatibility matrix](compatibility-matrix.md); neither those checks nor historical rasters establish general native Office parity.\n\n## Copy, paste, files, and galleries\n\nThe demo's **Copy OPF** dialog exports the whole presentation, the current slide with its design/catalogs/assets, or the selected JSON value. Choose readable JSON, compact JSON, or a Markdown code block for an LLM. The slide toolbar and selection inspector offer direct shortcuts. If clipboard permission is unavailable, **Select all** provides a manual copy fallback.\n\n**Add OPF** accepts a document, one slide, a slide array, a JSON value, or a single fenced JSON/OPF block. Paste into its text box, choose a `.opf`/`.json` file, drop a file on the editor, or load a public JSON URL. Preview first, then insert after the current slide, open a presentation, or replace selected content. Imports are validated and create one undo step. Normal copy/paste inside text fields remains native. Outside text fields, Cmd/Ctrl+V opens import review; Cmd/Ctrl+Shift+C opens Copy OPF; Cmd/Ctrl+O opens file import.\n\n**Browse galleries** includes 854 examples generated from the sibling PPTX.gallery checkout and a separate live PPTX.gallery registry. Search by name, category, or description. Select an entry to preview, copy its OPF, or insert it. **Manage galleries** adds/removes custom registry URLs; custom sources persist in this browser's local storage. Host defaults are defined in `opf-editor/examples/galleries.json`. The bundled snapshot is regenerated by `pnpm demo:editor`; it does not update in the background. Some presets are minimal definition examples rather than completed presentation slides.\n\nPublic PPTX.gallery detail links for layouts, colors, typography, themes, charts, backgrounds, narratives, blocks, and image treatments can be entered in the URL tab. Other sites should expose a direct OPF document or a registry JSON endpoint. Cross-origin servers must enable CORS. Requests omit credentials and referrers, are cancelable, and cap responses at 20 MB. A registry item's URL must stay on the configured origin; explicitly load another origin's URL when intended. The editor does not scrape arbitrary HTML pages or automatically load external fonts/catalog sources.\n\nA custom registry can mix inline OPF and relative document URLs:\n\n```json\n{\n \"name\": \"Team slides\",\n \"items\": [\n { \"id\": \"intro\", \"name\": \"Introduction\", \"category\": \"Team\", \"opf\": { \"slides\": [{ \"title\": \"Hello\" }] } },\n { \"id\": \"metrics\", \"name\": \"Metrics\", \"opfUrl\": \"./metrics.opf.json\" }\n ]\n}\n```\n\nImported documents should contain their required inline catalog records and assets. Inserting namespaces catalog IDs and conflicting asset/slide IDs, preserves the source slides' main design defaults, and leaves existing slides intact. It does not merge presentation-level speakers, organizations, or narrative metadata into the current deck. Open as a presentation to retain the complete source document. Conflicting or unresolved external catalog sources require a self-contained document before insertion.\n\nThe reusable npm APIs are browser-safe and independent of the demo UI:\n\n```js\nimport { parseOpfTransfer, serializeOpfTransfer, prepareOpfImport } from '@openpresentation/opf-editor/transfer';\nimport { loadOpfGallery, loadOpfGalleryItem } from '@openpresentation/opf-editor/galleries';\n\nconst markdown = serializeOpfTransfer(editor.document, {\n scope: 'slide', slideIndex: 0, format: 'markdown',\n});\nconst parsed = parseOpfTransfer(markdown);\nconst result = prepareOpfImport(editor.document, parsed, {\n mode: 'insert', slideIndex: 0,\n});\n// Host previews result.document before applying this single undoable change.\neditor.applyPatch([{ op: 'replace', path: '', value: result.document }], {\n source: 'import', rejectInvalid: true,\n});\n\nconst gallery = await loadOpfGallery('https://example.com/registry.json');\nconst document = await loadOpfGalleryItem(gallery.items[0], { gallery: gallery.url });\n```\n\nBoth gallery functions accept an `AbortSignal` and an injected `fetch` for host integrations and tests. Import/copy tests cover format round trips, invalid inputs, conflicting IDs and references, source isolation, and one-step undo; the generated 854-example snapshot is checked through insertion and SVG rendering.\n\n## All OPF properties\n\n**All properties** opens the schema-driven workspace beside a live SVG preview. Use Presentation, Current slide, Selection, or Design to navigate; add optional fields, select structured value forms, edit arrays/maps, and Apply a validated change with one undo step. Click content in the preview to locate its field. Nonvisual metadata remains part of the OPF document. A dirty draft must be applied or discarded before closing.\n\nThe `/schema` and `/schema-inspector` npm exports provide the reusable model and DOM inspector. `createSchemaInspector(container, {editor, path, onDraft})` exposes `navigate`, `commit`, `reset`, `destroy`, and read-only `document`/`dirty` getters. Use `onDraft` to render valid previews. The companion gallery `/spec` reference indexes the same 604 property definitions, and `/editor` embeds the shared browser build.\n\nSee [spec coverage](plans/spec-editor-coverage.md) for the distinction between complete field discovery and the remaining WYSIWYG rendering work.\n\n## Create, duplicate and delete content\n\nUse **Add content** in the editor toolbar, or the canvas button in Arrange mode. Choose a content kind, destination and insertion position. Starter content covers text, lists, charts, tables, metrics, quotes, code, timelines and groups. Image insertion accepts a local PNG/JPEG/WebP file or a source; local files are embedded as data URLs. Video insertion stores a source, but playback and native video export remain separate work. Source URLs and asset references still need the host's supported asset-resolution behavior.\n\nArrange handles also offer **Duplicate**, **Delete**, **Add after** and, for groups, **Add inside**. Duplication copies the complete content subtree while retaining asset references. Deletion prunes empty ancestor groups or their named region, preserving the slide and its metadata. Removing the last root block leaves a valid empty slide. Every operation preflights the complete candidate with the shared renderer and commits one undo step; stale forms are dismissed and strict overflow fails before mutation.\n\nAdding to implicit root content or a named-region leaf converts existing payloads into explicit blocks in the renderer's canonical field order. Headings, notes, design, metadata and neighboring regions stay intact. Named regions are kept in their existing positions; choose one as the destination. Existing composition weights remain attached to positions, so insertion/deletion can change which content occupies a weighted slot.\n\n```js\nimport {\n prepareBlockInsert, prepareBlockDuplicate, prepareBlockRemove, createContentBlock,\n listBlockContainers,\n} from '@openpresentation/opf-editor/layout';\nconst containers = listBlockContainers(editor.document, {includeImplicit: true});\nconst prepared = prepareBlockInsert(editor.document, containers[0].path,\n createContentBlock('table')); // omit index to append\n// Render prepared.document with your intended fonts before applying.\neditor.applyPatch(prepared.patches, {rejectInvalid: true});\n// Duplicate/remove take complete paths such as /slides/0/blocks/1.\n// canvas.openInsertMenu(containerPath?, index?) opens the browser palette.\n```\n\nThe headless helpers return `{document, patches, path, changed}` and include expected-value guards. Preserve those guards when applying patches. They need no browser, AI provider, account or hosted service. These helpers are published in editor 0.8.0; use the coordinated versions above and check installed exports when working with older packages. `/create-tests.html` and its installed-package equivalent exercise creation, image bytes, regions, duplication, deletion, strict-fit rejection, keyboard focus and undo.\n"
122
170
  },
123
171
  {
124
172
  "slug": "llm-authoring",
125
173
  "file": "docs/llm-authoring.md",
126
174
  "title": "Authoring OPF with an LLM",
127
- "markdown": '# Authoring OPF with an LLM\n\nWrite a complete JSON document with `name` and `slides`. Put visible words in slide fields, not presentation metadata. Use `*.opf.json` filenames and stable, unique slide `id` values when a deck will be revised repeatedly.\n\n```json\n{\n "name": "Launch decision",\n "slides": [{\n "id": "recommendation",\n "title": "Launch to the pilot group first",\n "composition": { "mode": "row", "weights": [2, 1] },\n "blocks": [\n { "items": ["Validate onboarding", "Measure activation", "Fix the largest drop-off"] },\n { "metric": { "value": 200, "label": "Pilot customers" } }\n ],\n "notes": "Confirm the rollout owner and checkpoint date."\n }]\n}\n```\n\nChoose one content structure per slide:\n\n- Root payloads for a simple slide: `text`, `items`, `image`, `chart`, `table`, `code`, `metric`, `quote`, or `timeline`.\n- `blocks` for a sequence that should reflow. Set `composition` only when an arrangement matters. Omit it to let the engine choose.\n- Promoted regions such as `left`, `center+right`, `top`, and `bottom` for spatially meaningful content. Regions must not overlap. Do not mix regions with root payloads or blocks.\n\nUse catalog IDs from the installed package or supply inline records in `catalogs`. A gallery route is a stable identifier, but an extended gallery layout may need the inline record included in the copied document. Do not invent an unresolvable layout or assume a network lookup will happen.\n\nTables use `{ "columns": ["Category", "Value"], "rows": [["A", 10]] }`. Charts put a `type` and the same tabular structure inside `chart.data`. Images use a source string or `{ "src": "...", "alt": "..." }`; use the top-level `assets` registry and `asset:<id>` references for reuse. The local renderer does not fetch remote sources.\n\n## Revision loop\n\n1. Validate with `validatePresentation`. Fix errors at their returned JSON paths. Check warnings for unknown catalog IDs.\n2. Render with `onDiagnostic` and inspect `text-overflow` / `small-cell` paths. Shorten text, reduce the number of blocks, change composition, or explicitly split the slide. Revalidate after edits.\n3. Use `composition.overflow: "error"` for a strict text-layout gate. It does not certify chart readability, font availability, or exact PowerPoint rendering.\n4. Inspect the actual preview and exported PPTX. Geometry is shared; font substitution and specialized objects can still differ.\n5. Apply focused JSON Patch edits through the editor session and retain undo history. Resolve stable slide IDs to current array indices before constructing patches; indices can change when slides are inserted or moved.\n\nPreserve factual content, sources, notes, and asset descriptions during layout repair. A fit diagnostic is a request to revise the slide; it is not permission to silently drop the end of a paragraph.\n\nSee [dynamic composition](dynamic-composition.md), [content payloads](content-payloads.md), and [design precedence](design-resolution.md).\n\nUse nested `blocks` to keep related content together. Put `composition` on the group to arrange its children, for example a column of evidence inside a row of sections. Read `composeSlide().groups` for group bounds and `items[].path` for precise leaf edits. Groups inherit readability constraints; splitting content into more levels does not make text smaller.\n\nUse `paginatePresentation(deck)` when a draft exceeds readable space. Review the returned ordinary OPF slides and source mappings before export. Pagination preserves source text exactly; it does not summarize or rewrite it. An atomic item that cannot fit produces a diagnostic for a targeted edit.\n'
175
+ "markdown": '# Authoring OPF with an LLM\n\nWrite a complete JSON document with `name` and `slides`. Put visible words in slide fields, not presentation metadata. Use `*.opf.json` filenames and stable, unique slide `id` values when a deck will be revised repeatedly.\n\n```json\n{\n "name": "Launch decision",\n "slides": [{\n "id": "recommendation",\n "title": "Launch to the pilot group first",\n "composition": { "mode": "row", "weights": [2, 1] },\n "blocks": [\n { "items": ["Validate onboarding", "Measure activation", "Fix the largest drop-off"] },\n { "metric": { "value": 200, "label": "Pilot customers" } }\n ],\n "notes": "Confirm the rollout owner and checkpoint date."\n }]\n}\n```\n\nChoose one content structure per slide:\n\n- Root payloads for a simple slide: `text`, `items`, `image`, `chart`, `table`, `code`, `metric`, `quote`, or `timeline`.\n- `blocks` for a sequence that should reflow. Set `composition` only when an arrangement matters. Omit it to let the engine choose.\n- Promoted regions such as `left`, `center+right`, `top`, and `bottom` for spatially meaningful content. Regions must not overlap. Do not mix regions with root payloads or blocks.\n\nUse catalog IDs from the installed package or supply inline records in `catalogs`. A gallery route is a stable identifier, but an extended gallery layout may need the inline record included in the copied document. Do not invent an unresolvable layout or assume a network lookup will happen.\n\nTables use `{ "columns": ["Category", "Value"], "rows": [["A", 10]] }`. Charts put a `type` and the same tabular structure inside `chart.data`. Images use a source string or `{ "src": "...", "alt": "..." }`; use the top-level `assets` registry and `asset:<id>` references for reuse. The local renderer does not fetch remote sources.\n\n## Revision loop\n\n1. Validate with `validatePresentation`. Fix errors at their returned JSON paths. Check warnings for unknown catalog IDs.\n2. Render with `onDiagnostic` and inspect `text-overflow` / `small-cell` paths. Shorten text, reduce the number of blocks, change composition, or explicitly split the slide. Revalidate after edits.\n3. Use `composition.overflow: "error"` for a strict text-layout gate. It does not certify chart readability, font availability, or exact PowerPoint rendering.\n4. Inspect the actual preview and exported PPTX. Geometry is shared; font substitution and specialized objects can still differ. Previews draw an open look-alike where the license-restricted font cannot be bundled (metric-compatible where one exists, for example Carlito for Calibri; visual-only for Aptos today), but the exported PPTX keeps the font name the user selected.\n5. Apply focused JSON Patch edits through the editor session and retain undo history. Resolve stable slide IDs to current array indices before constructing patches; indices can change when slides are inserted or moved.\n\nPreserve factual content, sources, notes, and asset descriptions during layout repair. A fit diagnostic is a request to revise the slide; it is not permission to silently drop the end of a paragraph.\n\nSee [dynamic composition](dynamic-composition.md), [content payloads](content-payloads.md), and [design precedence](design-resolution.md).\n\nUse nested `blocks` to keep related content together. Put `composition` on the group to arrange its children, for example a column of evidence inside a row of sections. Read `composeSlide().groups` for group bounds and `items[].path` for precise leaf edits. Groups inherit readability constraints; splitting content into more levels does not make text smaller.\n\nUse `paginatePresentation(deck)` when a draft exceeds readable space. Review the returned ordinary OPF slides and source mappings before export. Pagination preserves source text exactly; it does not summarize or rewrite it. An atomic item that cannot fit produces a diagnostic for a targeted edit.\n'
128
176
  },
129
177
  {
130
178
  "slug": "native-content-release-2026-09-09",
@@ -142,7 +190,7 @@ var docsData = Object.freeze([
142
190
  "slug": "next-agent-prompt-2026-09-15",
143
191
  "file": "docs/next-agent-prompt-2026-09-15.md",
144
192
  "title": "Copy/paste prompt for the next project owner",
145
- "markdown": "# Copy/paste prompt for the next project owner\n\nContinue OpenPresentation as its primary project owner. Build on accepted work,\nkeep changes committed and pushed through focused PRs as you go, and carry the\nnext bounded developer-readiness milestone through validation and release.\nDo not restart the ecosystem or redesign the published websites.\n\nRead OpenPresentation/opf docs/handoff-2026-09-15.md, docs/status-2026-09-15.md,\ndocs/plans/developer-adoption-20260915.md,\ndocs/plans/deferred-shaping-20260915.md and\ndocs/plans/ecosystem-objective-2026-09-09.md. Inspect current defaults, PRs,\nregistry and deployments: these files are dated checkpoints, not assumptions\nthat nothing has changed.\n\nRepositories: OpenPresentation/{opf,opf-render,opf-editor,opf-pptx} and\nData-Advantage/{openpresentation-site,pptx-gallery,pptx-dev}. Default branches\nare main except pptx-dev/master. Local clones are under /Users/michael/Source;\nuse GitHub if those paths do not exist. Use Node24, pinned lockfiles/package\nmanagers (pnpm10.33.2 core/site/gallery, pnpm11.1.3 pptx-dev, npm libraries),\neach AGENTS.md and relevant OPF skills. Schemas/catalogs are authoritative, but\nschema support does not certify editor, renderer or native export fidelity.\n\nAt this checkpoint npm versions are @openpresentation/opf0.10.0,\nopf-render0.8.0, opf-editor0.7.0, opf-pptx0.8.0 and cli0.8.0. Home/playground\nhave editable JSON, live previews, preview edits back to JSON, contextual\ncatalog choices and code-editor assistance. Preserve their appearance. Lint\nand contextual design contracts exist. The CLI supports create, validate,\nlint, revision-guarded JSON edits, data import, pagination, schemas/catalogs and\nsix-skill installation/update/status. Check actual help before promising other\ncommands; rendering/export is available through documented library/UI APIs.\n\nThe cleanup accepted eleven dependency updates and four furniture increments\n(core79, renderer20, editor17, PPTX34), and closed the Node26-types update.\nFour shaping PRs (core83, renderer21, editor21, PPTX35) conclude with docs/\nevidence only. Their prototypes are archived, not released. Check final PR/CI\nstates. Main's new furniture APIs still need coordinated package releases.\nThe public sites passed 41 production browser workflows at the handoff commits;\nretain evidence and rerun affected flows when changing them.\n\nFirst deliver a clear starting path from real published packages: release the\naccepted furniture increment in dependency order, then an independently\ninstallable example that authors JSON, validates/lints, resolves offline fonts,\ncomposes/paginates, edits with undo, previews and exports supported formats.\nCorrect stale API/version guidance in docs/skills, publish a truthful feature\nmatrix, and verify browser/Node boundaries without hidden sibling imports.\nUpdate release-plan and immutable verification refs; never overwrite versions.\nInspect actual tarballs, fresh installs and intended predecessor compatibility.\nAdopt features across the existing sites under core issue88 and verify canonical\nproduction routes, not only dependency manifests.\n\nPreserve each library's remote codex/archive-shaping-20260915 branch. Immutable\ncheckpoints: core 36ff66b3d62b39d7d27dcda022b7e79e541bd603;\nrenderer 343fb84223f4383ffe546157c6989ccc505c0acb;\neditor ae4cc6426b04c7ca428c1b4acacea2e11d99fa84;\nPPTX fbe9a73d012dbd51d65a39251e405e488651a70b.\nThey retain HarfBuzz/prepared glyphs, rich-source groups, variable/CFF work,\ncarets/graphemes/navigation/selection and tab/export experiments with evidence.\nResume from current main and port bounded changes; do not merge archives\nwholesale. Renderer issue24 tracks the Linux native-width contract failure:\n334.06213682353496px measured vs Chromium334.193115234375px at the unchanged\n0.1px gate. Rounding fixes Linux but breaks macOS; some platform widths cannot\nfit a common prediction within that tolerance. Define supported geometry/\npainting behavior and retain failures. No offsets, platform guesses, relaxed\nassertions or golden rewrites. The archived editor separately expects ten packed\nrich-input checks while thirteen pass: repair and rerun that harness on resume.\n\nNative PowerPoint acceptance is split into core issue87 and its plan. Preserve\nimage opening, provenance edit/save/reopen, tabs, notes-master ordering and\nphysical font identity/embedding gaps. Serialization/self-import are not native\nacceptance. Do not retry Windows COM or kill Office processes until host recovery\nis confirmed. Do not use/distribute restricted Aptos4.40 without compatible\nexplicit permission. Broader fonts/IME/bidi/fallback, layout repair and complete\nvisual editor interactions remain planned. Current PDF is raster-backed;\nselectable vector PDF, general SVG and semantic Mermaid follow font reliability.\n\nKeep the ecosystem open, provider-neutral and locally usable without an account\nor model call. No mandatory embedded AI design agent. Preserve text, images,\nUnicode, whitespace, formatting, source mappings, reading order, intent and\nundo. Bound layout repair and emit actionable diagnostics rather than dropping\ncontent. Review complete rendered slides, not only metrics or hashes. Separate\nsource, packed consumer, registry, browser, deployment and native acceptance.\n\nUse accurate progress updates and proceed with authorized reversible work\nwithout repeated permission questions. Preserve existing work, use codex/\nbranches and focused PRs, and merge only validated scope. Store incomplete work\nand failures in durable roadmap issues with evidence. Report what is committed,\nmerged, released, deployed, deferred and next. The broad ecosystem objective is\nnot complete merely because the PR queue is empty. Finish with an updated handoff.\n"
193
+ "markdown": "# Copy/paste prompt for the next project owner\n\nStart with [the current handoff](handoff-2026-09-21.md), [quickstart](quickstart.md)\nand [compatibility matrix](compatibility-matrix.md). The filename is historical;\nthis entrypoint was updated 21 September 2026. The broad ecosystem goal and the\ndeveloper-ready milestone remain incomplete.\n\nPublished Node 24 train: `@openpresentation/opf@0.11.0`, `cli@0.9.0`,\n`opf-render@0.9.0`, `opf-pptx@0.9.1`, `opf-editor@0.8.0`. ColorRef and shared\nfurniture are shipped. Do not publish another version to correct these docs.\nUse release-plan.json for exact versions and immutable harness commits. Check\nactual registry/remote/deployment state before relying on older dated evidence.\n\nRepositories: OpenPresentation/{opf,opf-render,opf-editor,opf-pptx} and\nData-Advantage/{openpresentation-site,pptx-gallery,pptx-dev}. Defaults are main\nexcept pptx-dev/master. Sync clean checkouts first, preserving local work.\nUse Node 24, pinned package managers/lockfiles, AGENTS.md and relevant OPF skills.\nThe schema/catalogs are authoritative for format support; renderer/editor/native\nfidelity requires separate evidence.\n\nThe public sites carry the shipped train and issue88 features. Verify remaining\nacceptance from the current handoff before closing issue88. Preserve appearance.\nLeave the five geometry drafts together: core94, renderer27, editor25, PPTX42,\nsite40. Do not merge the site half alone. Native Header/Footer docs drafts\ncore92/PPTX41 remain roadmap; do not implement p:hf in this milestone. Furniture\nexports as editable tagged slide shapes (OPF_FURNITURE_V1), not native HF.\n\nRenderer issue24 stays deferred at the unchanged 0.1px gate. Do not round away\nresiduals, add platform offsets or rewrite goldens to hide failures. Preserve\nremote codex/archive-shaping-20260915 branches: core36ff66b3d62b39d7d27dcda022b7e79e541bd603,\nrenderer343fb84223f4383ffe546157c6989ccc505c0acb,\neditor ae4cc6426b04c7ca428c1b4acacea2e11d99fa84,\nPPTX fbe9a73d012dbd51d65a39251e405e488651a70b. Port bounded changes from current\nmain; never merge these unfinished prototypes wholesale. The archived editor's\npacked rich-input assertion expects ten while thirteen workflows pass; repair\nand rerun when resuming it.\n\nNative Office issue87 retains picture open/save/reopen, current-content\nprovenance, tabs, notes-master order and physical font identity/embedding gaps.\nDo not retry Windows COM or kill Office processes before owner-confirmed host\nrecovery. Restricted Aptos4.40 requires compatible explicit permission.\nSerialization and controlled reimport do not certify Office.\n\nLater, not in this checkpoint: move docs/fixtures/color-references.opf.json\ninto examples with reviewed renderer golden/corpus growth (126 to 127+),\noptional PPTX schemeClr/theme writing, and editor canvas named-color fidelity.\nBroader IME/bidi/fonts, bounded repair, full visual-editor coverage, selectable\nvector PDF and semantic SVG/Mermaid remain roadmap work. PDF is raster-backed.\n\nKeep essential work local, offline and provider-neutral without account/model\ncalls. Preserve authored content, whitespace, rich formatting, metadata, source\nmappings, reading order, explicit adjustments and undo. Bound layout repair and\nreturn actionable diagnostics instead of dropping content. Review visual changes.\nSeparate source, fresh installed packages, browser/CI, deployment, native and\nfont gates. Keep evidence and handoffs current; do not mark the overall goal\ncomplete while required compatibility or release work remains unresolved.\n"
146
194
  },
147
195
  {
148
196
  "slug": "open-ecosystem",
@@ -156,23 +204,29 @@ var docsData = Object.freeze([
156
204
  "title": "PR backlog and public adoption checkpoint \u2014 2026-09-09",
157
205
  "markdown": "# PR backlog and public adoption checkpoint \u2014 2026-09-09\n\nThe user authorized resolving older PRs as well as completing active adoption work. Closures below preserve branches and explicit remaining work; they do not hide security alerts or claim deferred features were implemented. The broader deterministic layout/font objective remains active.\n\n## Current disposition\n\nAll nine original PRs have a disposition below. Website recovery #18 and YAML migration #28 are now both merged and publicly verified. This section supersedes the pending gates retained in historical review notes.\n\nYAML #28 merged as `b922f2f89fdb1e68f0e71a7f1df638be9c5314d4`, tree-identical to reviewed `dfdede29458bea1afb13f7f07d5e1079f9e9e515`. Linux/Windows application CI `34386017908`, artifact CI `34386017825` and Bugbot pass. All eight Edge workflows pass on exact preview `dpl_6dXvANdwpkPgRPQ31oSKhrGqFHbx` (25.1s) and actual production `dpl_9B4YfiTrX3YCvK632T9qwtPTYpMs` (28.8s), READY on the merge. All 33 deployed font files and licenses match registry renderer 0.5.1; [new public font report](evidence/pptx-dev-yaml-fonts-2026-09-09.json). This is offline editing/export/reimport evidence, not new native raster-equivalence evidence. The Arabic glyph gap remains explicit.\n\nThe owner's Data-Advantage Actions budget increase restored jobs. Subsequent Linux apt hash failures were resolved with a digest-pinned official Playwright image matching the installed 1.63.0 package; all Linux browser tests execute in it and native Windows checks remain. Core Windows harness #44 also merged as `94e4e019d28a1e16ac7e192b564596077dc2a6fa`, after coordinated/core Node 20/24 and Windows/macOS packed-install/CLI CI plus review passed. Its earlier missing-helper fixture failure is fixed. No security alerts or required checks were suppressed, and no OPF package was republished.\n\n## Completed production milestone\n\n- Core PR #40 merged as `4913850e46f0e5fc5e7d17639d6d052c0644023b`, tree-identical to reviewed `a76dd2d57ab5b3da6e0a1e85d4a0a4e1e08025f7`. Node 20/24 package/coordinated CI `34373052810` / `34373052839` and Bugbot pass.\n- Website PR #17 merged as `47e5b4c611a0de7ae6fbd9838bde0c800882ad87`, tree-identical to reviewed `56069382de43c504e871cfc9d1e9cc88e83fb1ab`. CI `34372811378` and Bugbot pass. Exact preview `dpl_Hy7GLS92BjaaZt73unQG7E6sTJjp` passes four Edge workflows (8.3s); production `dpl_VkqwZ3Wj223SCWPCKX3oqowyT2PL` is READY on the merge and all four public workflows pass (8.1s). Published changelog, actual installation command/six complete skills, mobile overflow and byte-matched registry showcase downloads are verified.\n- Gallery PR #23 merged as `b28d33564d7da2836f5c5d2e060ea461a7ac96bb`, tree-identical to reviewed `58dd6957c7508d9459007cea4616e9c811f17383`. CI `34372810518` and Bugbot pass. Exact preview `dpl_414aa1sAZehLg7xgdHefMH5zWhcv` passes both Edge workflows (10.3s); production `dpl_o1NLnSWDsaenu6pSRXfbzXRwLcHp` is READY on the merge and both public workflows pass (12.3s), including all eight bundle resources and offline author/edit/undo/OPF/PPTX export/reimport.\n- pptx.dev PR #25 merged as `ccb8f496eb6974886cc6130724ada1a19e3e28dc`, tree-identical to reviewed `7b555a472e0e024c8cdf227e8b20ab4b8e3e7f68`. Linux/Windows application CI `34378313574` and artifact CI `34378313595` pass, as does Bugbot. Exact READY preview `dpl_5qQCSwRjygXAjpu2XKiTNBuHR7sX` passes seven Edge workflows (22.3s). Production `dpl_6LKa8XQ2435ifGbwBZNmzh7gdgkK` is READY on the merge; all seven public Edge workflows pass (29.8s), and real sign-in mounts with no page errors or submission. All 33 public font files and licenses match the installed renderer 0.5.1 registry package; [hash-bound report](evidence/pptx-dev-adoption-fonts-2026-09-09.json).\n\nThese are actual deployed renderer 0.5.1/PPTX 0.5.2 adoption results. No package was republished. Existing native fidelity limits remain unchanged.\n\nWebsite recovery PR #18 subsequently merged as `b098688a5c1c365d9f7c614703b692e6b62a5624`, tree-identical to reviewed `14bb8559e26700b00e2a0a1459b4abd2151e2503`. CI `34379591534` and Bugbot pass. Exact READY preview `dpl_2r7ZbYpH3L5ZcsvpoYXFhgxkttrC` passes six Edge workflows (22.4s); exact READY production `dpl_GFPaPkbLuRuN7AKoGfqmjvzPbtGd` passes all six public workflows (19.0s). The recovered playground, hosted references, complete clipboard titles and every advertised reference URL are now verified live. [Final review](https://github.com/Data-Advantage/openpresentation-site/pull/18#issuecomment-5605743802).\n\nThe actual Author export downloaded during the public pptx.dev run also passes PowerPoint 16 native text/table edit, save/reopen and schema-valid reimport. Its raster was visually inspected; [public-export native report](evidence/pptx-dev-public-native-2026-09-09.json) binds the source, native-saved file and raster hashes to production `ccb8f496`. This one-slide native editability test does not establish arbitrary-file roundtrip or raster equivalence.\n\n## Older PR disposition\n\n- Core [#16](https://github.com/OpenPresentation/opf/pull/16) (TypeScript 7) closed without merging. Reproduced tsup 8.5.1 / legacy compiler API declaration failure on Windows Node 24.20.0. [Issue #41](https://github.com/OpenPresentation/opf/issues/41) preserves tooling migration, packed consumer checks and minimum-runtime acceptance criteria.\n- pptx.dev [#19](https://github.com/Data-Advantage/pptx-dev/pull/19) (Commander 15) closed without merging. Rechecked registry `engines`: Node >=22.12.0 conflicts with the CLI's >=20 promise. [Issue #26](https://github.com/Data-Advantage/pptx-dev/issues/26) tracks a Commander 14.0.3 review and minimum-runtime CI, without dropping Node 20 or suppressing future advisories.\n- pptx.dev draft [#6](https://github.com/Data-Advantage/pptx-dev/pull/6) closed without merging. [Issue #27](https://github.com/Data-Advantage/pptx-dev/issues/27) inventories its 27-file copy/navigation work and requires reconciliation with actual anonymous local workflows. Old claims that Author is a shell and every workbench is REST-powered must not replace current behavior. This is explicit remaining work, not a completed copy migration.\n- Website [#4](https://github.com/Data-Advantage/openpresentation-site/pull/4) closed as superseded by [#18](https://github.com/Data-Advantage/openpresentation-site/pull/18), branch `codex/reference-playground-recovery-20260909`, `09ece59c0eb2866815cac1c10640a648c3798784`. The recovery merge preserves the original history, adds the missing validator playground and hosted reference pages, fixes the missing clipboard title, and extends the existing generated LLM bundle without downgrading dependencies or duplicating routes. Build: 624 pages/619 unique sitemap URLs. Six local Edge workflows pass (6.6s); exact READY preview `dpl_SUesrpxHpEugpu74qejmReznpFhg` passes all six (14.3s), including actual clipboard text with Windows newline normalization, TOC targets/scrolling, offline validation and current schema/example discovery. CI `34375590128` passes; final Bugbot/merge/public deployment remain gates.\n- Core [#13](https://github.com/OpenPresentation/opf/pull/13) merged as `15f6bec9bdb1a21420c658aaa8da9449b491c38c`, tree-identical to reviewed `161401dafd6ce4f2d1b50b3d864b6afd8765a24d`, with main merged, lock conflicts resolved and current security patches retained. Core/CLI typechecks and tests pass on Windows Node 20/24 after applying the existing LF checkout policy to the old worktree. The new TypeScript consumer test reaches payloads through the exported `Presentation` type: valid nested/rich content compiles; arbitrary extra object properties/indexing is rejected, matching the existing schema. The observable type tightening is explicitly Unreleased in CHANGELOG; never republish 0.7.0. Core Node 20/24 CI `34376506941`, coordinated renderer/editor/converter Node 20/24 CI `34376506991`, and Windows/macOS Node 20/24 CLI CI `34376506928` all pass. [Final compatibility review](https://github.com/OpenPresentation/opf/pull/13#issuecomment-5605258810). An unrelated Windows local-link harness failure (`symlink` privilege / npm batch spawning) remains to fix separately.\n- pptx.dev [#20](https://github.com/Data-Advantage/pptx-dev/pull/20) closed as superseded by [#28](https://github.com/Data-Advantage/pptx-dev/pull/28), branch `codex/yaml5-migration-20260909`, `6c19b11`. The official v5 migration changes exports, bundled types and loader semantics; namespace imports, explicit core schema with merge support, empty frontmatter handling and preservation/security tests are implemented. Browser tests exposed and fixed JSON downloads containing the YAML/Markdown buffer, font disposal preventing offline malformed-source recovery, and deferred YAML grammar loading failing on first offline use. On the merged adoption base: frozen installation, 596 tests/61 files, typecheck, production build, audit with zero reported vulnerabilities and eight local Edge workflows (18.6s) pass. CI/review/exact preview/public verification remain gates. Multilingual content is preserved in source/downloads, while the default Carlito missing-Arabic-glyph error remains explicit; this does not establish multilingual rendering.\n\n## Historical review corrections and gates, resolved above\n\nThe owner restored the Data-Advantage Actions budget after the initial YAML failure below. Run `34382820569` attempt 2 is executing on Linux and Windows, and final Bugbot review has been requested once. The newest audit finds only three active PRs across all seven repositories: YAML #28 and core layout #43 / Windows harness #44. All nine original PRs have a disposition. Layout #43 subsequently passed every check/review and merged as `1cc549183c6fd2e885f06410471e7142be69410a`; no package was published.\n\nMerged PR [#25](https://github.com/Data-Advantage/pptx-dev/pull/25) clears stale ghost proposals/format errors after shared navigation. Its first preview caught a delayed account-widget chunk after going offline. Disabling UI prefetch globally broke actual sign-in; a narrowed version still risked signed-in widgets and client navigation. Both changes were rejected before merge. The final revision restores the original Clerk provider entirely and changes the offline worker test to await the configured SDK's loaded UI version. CI retains browser failure traces and gives initial font/preview readiness a bounded 20-second wait. Both review threads are resolved; [final exact-head review](https://github.com/Data-Advantage/pptx-dev/pull/25#issuecomment-5605496916). Anonymous checks do not establish signed-in account behavior.\n\nWebsite #18's final review found an advertised `/docs/reference/cli` URL without a corresponding source route. Hosted discovery now uses exactly the source-doc directory/filter, and a compatibility alias cannot overwrite a real hosted CLI document. Absent/present CLI fixture checks plus real HTTP checks of every advertised reference URL pass. A stalled review was manually restarted once; the final review and public verification are complete as recorded above.\n\nYAML #28 is now `e9f21ec007d3e382234c7bd8e062bc062fad471f`. Non-finite values and cyclic aliases return actionable errors instead of changing JSON content. Linux CI `34380362278` exposed unconditional Escape interception; `b248297` fixed it and passed both Linux/Windows CI `34381496268`, eight exact-preview workflows (26.1s) and three repetitions of the four Inspector/worker workflows (12 runs). Review then found comment-only Markdown frontmatter. The latest fix uses the parser to distinguish zero/one/multiple documents and preserves exact title/body through comment-only Markdown, SVG recovery and actual JSON downloads. Local 598 tests/61 files, fresh build and all eight Edge workflows pass. Exact READY preview `dpl_CEw5yBz6cv622CbHvHfzNYvVqz5x` passes eight workflows (25.8s). CI `34382820569` ran no steps: both jobs were refused because GitHub reports failed account payments or a spending-limit issue. Keep the PR open; restore GitHub Billing & plans, rerun exact-head Linux/Windows CI, finish review, then merge and verify production. This migration is not deployed publicly.\n\nA fresh GitHub audit finds zero open Dependabot security alerts in all seven repositories. No security alert has been dismissed or disabled. Notification grouping/scheduling from the prior milestone remains. The authenticated Vercel CLI resolved the previous dashboard dependency: all three public-site projects now have `gitComments.onCommit=false` and `gitComments.onPullRequest=false`, with deployment creation still enabled and commit status reporting not disabled. Fresh read-back verification is recorded in [the settings report](evidence/vercel-comment-settings-2026-09-09.json). The existing status checks retain deployment results/preview links; only redundant comment notifications were changed. GitHub security alerts, CI and review checks remain enabled.\n"
158
206
  },
207
+ {
208
+ "slug": "quickstart",
209
+ "file": "docs/quickstart.md",
210
+ "title": "Developer quickstart",
211
+ "markdown": "# Developer quickstart\n\nA new developer can install the **published** OPF packages into a fresh Node 24\nproject and author, lint, compose, paginate, edit with undo, preview and export\na representative deck. No account, model call, or sibling repository checkout\nis required.\n\nThis is a documented supported subset, not universal Office or all-feature\nparity. See the [compatibility matrix](compatibility-matrix.md) for what is\nshipped versus deferred.\n\n## Versions\n\nPin the coordinated set from `release-plan.json` (currently core **0.11.0**,\nCLI **0.9.0**, renderer **0.9.0**, PPTX **0.9.1**, editor **0.8.0**). All of these packages declare\n`engines.node: 24.x`.\n\n```sh\nnode -v # must be 24.x\nnpm install @openpresentation/opf@0.11.0 \\\n @openpresentation/opf-render@0.9.0 \\\n @openpresentation/opf-editor@0.8.0 \\\n @openpresentation/opf-pptx@0.9.1 \\\n @openpresentation/cli@0.9.0\n```\n\nCopy [`docs/quickstart/developer-quickstart.opf.json`](quickstart/developer-quickstart.opf.json)\ninto that project as `deck.opf.json`. That file is a docs fixture, not one of\nthe 126 decks in `@openpresentation/opf/examples`. Verify the install came from the registry\n(`package-lock.json` `resolved` URLs start with `https://registry.npmjs.org/`)\nand that you did not add `file:` dependencies on this repository.\n\nThe [format card](format-card.md) describes ColorRef, named variables and the\ncurrent source contract. `opf bundle` can inline resolved catalog records for\nportable offline authoring; it does not download remote assets. Keep the\nColorRef docs fixture outside the 126-deck example/golden corpus in this update.\n\n## Author, validate and lint\n\n```sh\nnpx --no-install opf --version\nnpx --no-install opf validate deck.opf.json\nnpx --no-install opf lint deck.opf.json\n```\n\nThe CLI bundles schema, catalogs and lint. It does **not** render slides.\n`opf --version` reports the CLI and bundled core. Successful validation is not\nvisual verification.\n\nLibrary equivalents:\n\n```js\nimport { readFile } from 'node:fs/promises';\nimport { validatePresentation, lintSource } from '@openpresentation/opf';\n\nconst source = await readFile('deck.opf.json', 'utf8');\nconst document = JSON.parse(source);\nconsole.log(validatePresentation(document));\nconsole.log(lintSource(source));\n```\n\n## Offline fonts, composition, pagination\n\n```js\nimport { composeSlide, paginatePresentation, fontSchemes, resolveFontFamilies } from '@openpresentation/opf';\nimport { prepareNodeFonts } from '@openpresentation/opf-render/fonts-node';\n\nconst { options } = await prepareNodeFonts({ pack: 'base' });\nconst fonts = resolveFontFamilies(fontSchemes.find(scheme => scheme.id === 'roboto'));\nconst geometry = composeSlide(document.slides[0], { presentation: document, fonts, ...options });\nconst { presentation, pages } = paginatePresentation(document, { fonts, ...options });\n```\n\n`prepareNodeFonts({ pack: 'base' })` loads the bundled Roboto faces for\n`design.fontScheme: 'roboto'`. Pass `fonts` from that scheme into `composeSlide`\nwhen you also pass `textMeasurement`; otherwise furniture falls back to\n`sans-serif` and the registry has no matching face. `paginatePresentation`\nresolves catalog font schemes itself. The helper does not install system fonts\nor change the authored scheme. Reuse the same `options` for SVG preview and\nPPTX export.\n\nShared headers and footers use `furniture-flow-v2`. Body content stays between\n`geometry.furniture.headerBottom` and `geometry.furniture.footerTop`.\n\nPagination returns a new presentation plus source mappings. It preserves\nauthored text, whitespace and reading order; it does not drop overflowed\ncontent.\n\n```sh\nnpx --no-install opf paginate deck.opf.json paginated.opf.json\n```\n\n## Edit with undo\n\n```js\nimport { createEditorSession } from '@openpresentation/opf-editor';\n\nconst editor = createEditorSession(document, { rejectInvalid: true });\nconst original = editor.document.slides[0].title;\neditor.set('slides.0.title', 'Edited title');\neditor.undo();\n// original title, including whitespace, is restored\n```\n\nThe CLI can apply JSON Patch edits (`opf edit`) but has no persistent undo\nhistory. Use the editor session or version control for undo.\n\n## Preview and export\n\n```js\nimport { renderSvgDeck, svgToPng, svgToPdf } from '@openpresentation/opf-render';\nimport { toPptx } from '@openpresentation/opf-pptx';\n\nconst svgs = renderSvgDeck(presentation, options);\nconst png = await svgToPng(svgs[0], options);\nconst pdf = await svgToPdf(svgs, options);\nconst pptx = await toPptx(presentation, options);\n```\n\n`renderSvg` / `renderSvgDeck` are the local preview. PNG and PDF rasterize that\nSVG; **PDF is raster-backed** in this release (not selectable vector text).\n`toPptx` is the supported editable PowerPoint export from OPF. Shared\nheaders/footers in that file are tagged slide shapes (`OPF_FURNITURE_V1`), not\nnative Office Header/Footer objects (`p:hf` / notes master). Opening the file\nin Microsoft PowerPoint, compiling furniture into real Header/Footer objects,\nand round-tripping native fidelity is\n[issue 87](https://github.com/OpenPresentation/opf/issues/87), not this\nquickstart.\n\nBrowser preview uses the same SVG core plus\n`@openpresentation/opf-render/fonts-browser` and\n`@openpresentation/opf-editor/canvas`. Load the same font bytes the Node helper\nresolved. Do not fetch fonts from the network at render time.\n\n## Prove it\n\nFrom this repository, after a normal `pnpm install`:\n\n```sh\nnode scripts/test-developer-quickstart.mjs\n```\n\nThat script creates an empty temp project, installs the published versions from\nthe npm registry, copies this example, and asserts validate, lint, offline\nfonts, furniture composition, pagination, undo, SVG, PNG, raster PDF and PPTX.\nIt fails if any package is a `file:` or workspace link.\n\n## What this does not cover\n\n- Renderer native-width residuals:\n [opf-render#24](https://github.com/OpenPresentation/opf-render/issues/24)\n- Native PowerPoint open/edit/save/reopen and real Office Header/Footer (`p:hf`):\n [opf#87](https://github.com/OpenPresentation/opf/issues/87)\n- Remaining GitHub [issue 88](https://github.com/OpenPresentation/opf/issues/88)\n checklist (the Inspector overlay/json-options, gallery Playground+Editor\n links, and Header & footer playground example are already live on\n production; the issue stays open)\n- Archived font-shaping prototypes (not in the published runtime)\n- Selectable vector PDF, general SVG diagrams, and Mermaid\n"
212
+ },
159
213
  {
160
214
  "slug": "release-process",
161
215
  "file": "docs/release-process.md",
162
216
  "title": "OPF Release Process",
163
- "markdown": "# OPF Release Process\n\nUse Node 24 (`24.x`) for all future source, candidate and registry verification.\nThe next releases must document the [Node 24 migration](migrations/node24.md)\nand use new versions. Historical dual-runtime release records remain unchanged.\n\nThis document is the release runbook for the public JavaScript package,\n[`@openpresentation/opf`](https://www.npmjs.com/package/@openpresentation/opf).\n\nThe canonical release path is:\n\n1. Merge the release commit to `main`.\n2. Push a semver tag whose name matches the package version.\n3. Let GitHub Actions publish to npm through npm trusted publishing.\n4. Verify npm and the automatically generated GitHub release notes.\n\n## Release Preconditions\n\nBefore tagging, confirm that the release commit on `main` already contains:\n\n- `packages/javascript/package.json` with the intended version.\n- `CHANGELOG.md` with the matching release section.\n- Passing `OPF CI` on the release commit.\n\nThe publish workflow validates the tag name against\n`packages/javascript/package.json`, so the tag must point at the release commit.\n\n## Tag And Publish\n\nUse the `opf-vX.Y.Z` tag form for the package release:\n\n```sh\ngit checkout main\ngit pull origin main\ngrep '\"version\"' packages/javascript/package.json\ngit tag opf-vX.Y.Z\ngit push origin opf-vX.Y.Z\n```\n\nFor example, version `0.3.0` used:\n\n```sh\ngit tag opf-v0.3.0\ngit push origin opf-v0.3.0\n```\n\nPushing the tag triggers `.github/workflows/npm-publish.yml`. The workflow:\n\n- runs on tags matching `opf-v*` or `@openpresentation/opf@v*`\n- installs dependencies with pnpm on Node 24\n- verifies the tag matches `packages/javascript/package.json`\n- runs typecheck and tests\n- runs the npm package dry-run check\n- publishes from `packages/javascript` with `npm publish --access public`\n\nDo not rerun a successful publish for the same version. npm package versions are\nimmutable; a second publish for an already-published version should fail.\n\n## Trusted Publishing\n\nnpm publishing is configured to use GitHub Actions OIDC trusted publishing, not\na long-lived npm token.\n\nExpected npm package trusted-publisher settings:\n\n| Setting | Value |\n|---|---|\n| Package | `@openpresentation/opf` |\n| Publisher | GitHub Actions |\n| Organization/repository | `OpenPresentation/opf` |\n| Workflow filename | `npm-publish.yml` |\n| Environment | empty, unless the workflow is later moved behind a GitHub Environment |\n| Permission | `npm publish` |\n\nExpected workflow settings:\n\n```yaml\npermissions:\n contents: read\n id-token: write\n```\n\nThe publish step should not set `NODE_AUTH_TOKEN`:\n\n```yaml\n- name: Publish to npm\n working-directory: packages/javascript\n run: npm publish --access public\n```\n\nIf a future release fails with npm authentication errors, check the npm\ntrusted-publisher settings first. Only use an `NPM_TOKEN` repository secret as a\ntemporary fallback, and remove or revoke it once OIDC publishing works again.\n\n## Verify The Release\n\nAfter the workflow completes, verify npm:\n\n```sh\nnpm view @openpresentation/opf version\n```\n\nThe output should equal the package version that was tagged.\n\nSpot-check the validator API from a clean project or temporary directory:\n\n```sh\nnpm install @openpresentation/opf@X.Y.Z\nnode --input-type=module -e \"import {validatePresentation} from '@openpresentation/opf'; console.log(validatePresentation({name:'t', narrative:'not-a-real-id', slides:[{title:'t'}]}).warnings)\"\n```\n\nThe expected result is one warning about an unknown narratives catalog id.\n\n## GitHub Release Notes\n\nThe core tag workflow creates a GitHub Release from the matching changelog section after publishing. Verify that release after npm is verified. If release creation failed, create the missing release for the existing tag:\n\n```sh\ngh release create opf-vX.Y.Z \\\n --repo OpenPresentation/opf \\\n --title '@openpresentation/opf X.Y.Z' \\\n --notes-file /path/to/release-notes.md\n```\n\nUse the matching `## X.Y.Z` section from `CHANGELOG.md` as the release notes.\n\n## Troubleshooting\n\nIf the tag/version check fails, the tag does not point at the release commit or\nthe tag name does not match `packages/javascript/package.json`. Delete the bad\nlocal and remote tag, fetch `main`, and tag the correct commit:\n\n```sh\ngit push origin :refs/tags/opf-vX.Y.Z\ngit tag -d opf-vX.Y.Z\ngit checkout main\ngit pull origin main\ngit tag opf-vX.Y.Z\ngit push origin opf-vX.Y.Z\n```\n\nIf tests fail, fix the code on `main`, create a new release commit, and move the\ntag only if npm has not already published that version.\n\nIf npm publish fails with `ENEEDAUTH`, confirm:\n\n- npm has a trusted publisher for `OpenPresentation/opf`\n- the trusted publisher uses workflow filename `npm-publish.yml`\n- `.github/workflows/npm-publish.yml` has `id-token: write`\n- the publish job is running on a modern Node/npm toolchain\n\nIf npm publish fails after the version is already present on npm, do not retry\nthe same publish. Verify the package and treat the failure as a duplicate\npublish attempt.\n"
217
+ "markdown": "# OPF Release Process\n\nUse Node 24 (`24.x`) for all future source, candidate and registry verification.\nThe next releases must document the [Node 24 migration](migrations/node24.md)\nand use new versions. Historical dual-runtime release records remain unchanged.\n\nThis document is the release runbook for the public JavaScript package,\n[`@openpresentation/opf`](https://www.npmjs.com/package/@openpresentation/opf).\n\nThe canonical release path is:\n\n1. Merge the release commit to `main`.\n2. Push a semver tag whose name matches the package version.\n3. Let GitHub Actions publish to npm through npm trusted publishing.\n4. Verify npm and the automatically generated GitHub release notes.\n\n## Agent authorization and coordinated release order\n\nThe owner authorized agents to prepare and publish npm releases on 2026-09-29\nand for future releases whenever a release is required. This does not waive any\ngate: a release still needs a merged release-prep PR, green required checks and\nthe verification below. Agents keep publishing on the trusted-publishing\nworkflows (GitHub Actions OIDC with `--provenance`); a local `npm publish` is a\nfallback only when a workflow cannot run, and it loses the provenance\nattestation that every previous version carries.\n\nThe engine packages depend on each other, so publish in this order and wait for\neach version to appear on the registry before starting the next:\n\n1. `@openpresentation/opf` (this repository, `opf-vX.Y.Z` tag).\n2. `@openpresentation/opf-render` (`opf-render-vX.Y.Z` tag) and\n `@openpresentation/opf-pptx` (`opf-pptx-vX.Y.Z` tag). Both depend on core; PPTX\n also devDepends on the renderer, so publish the renderer first.\n3. `@openpresentation/opf-editor` (`opf-editor-vX.Y.Z` tag), which depends on core\n and peers/devDepends on the renderer and PPTX.\n\nEach sibling's release-prep PR raises its dependency floors to the just-published\nversions. Its lockfile can only be refreshed after the upstream version exists on\nnpm (`npm install --package-lock-only`), so merge sibling release PRs only after\nthe upstream publish. `@openpresentation/cli` bundles core and is released\nseparately by `cli-publish.yml` (`cli-vX.Y.Z`) when a fresh bundle is needed.\n\nRelease-prep PRs contain only version bumps, changelog entries, dependency ranges\nand lockfile changes (plus current-instruction docs). After the whole set is on\nthe registry, a follow-up docs change updates `release-plan.json`, the\ncompatibility matrix and the quickstart to the published set, and the gallery\nconsumer dependencies are bumped.\n\n## Release Preconditions\n\nBefore tagging, confirm that the release commit on `main` already contains:\n\n- `packages/javascript/package.json` with the intended version.\n- `CHANGELOG.md` with the matching release section.\n- Passing `OPF CI` on the release commit.\n\nThe publish workflow validates the tag name against\n`packages/javascript/package.json`, so the tag must point at the release commit.\n\n## Tag And Publish\n\nUse the `opf-vX.Y.Z` tag form for the package release:\n\n```sh\ngit checkout main\ngit pull origin main\ngrep '\"version\"' packages/javascript/package.json\ngit tag opf-vX.Y.Z\ngit push origin opf-vX.Y.Z\n```\n\nFor example, version `0.3.0` used:\n\n```sh\ngit tag opf-v0.3.0\ngit push origin opf-v0.3.0\n```\n\nPushing the tag triggers `.github/workflows/npm-publish.yml`. The workflow:\n\n- runs on tags matching `opf-v*` or `@openpresentation/opf@v*`\n- installs dependencies with pnpm on Node 24\n- verifies the tag matches `packages/javascript/package.json`\n- runs typecheck and tests\n- runs the npm package dry-run check\n- publishes from `packages/javascript` with `npm publish --access public --provenance`\n\nDo not rerun a successful publish for the same version. npm package versions are\nimmutable; a second publish for an already-published version should fail.\n\n## Trusted Publishing\n\nnpm publishing is configured to use GitHub Actions OIDC trusted publishing, not\na long-lived npm token.\n\nExpected npm package trusted-publisher settings:\n\n| Setting | Value |\n|---|---|\n| Package | `@openpresentation/opf` |\n| Publisher | GitHub Actions |\n| Organization/repository | `OpenPresentation/opf` |\n| Workflow filename | `npm-publish.yml` |\n| Environment | empty, unless the workflow is later moved behind a GitHub Environment |\n| Permission | `npm publish` |\n\nExpected workflow settings:\n\n```yaml\npermissions:\n contents: read\n id-token: write\n```\n\nThe publish step should not set `NODE_AUTH_TOKEN`:\n\n```yaml\n- name: Publish to npm\n working-directory: packages/javascript\n run: npm publish --access public\n```\n\nIf a future release fails with npm authentication errors, check the npm\ntrusted-publisher settings first. Only use an `NPM_TOKEN` repository secret as a\ntemporary fallback, and remove or revoke it once OIDC publishing works again.\n\n## Verify The Release\n\nAfter the workflow completes, verify npm:\n\n```sh\nnpm view @openpresentation/opf version\n```\n\nThe output should equal the package version that was tagged.\n\nSpot-check the validator API from a clean project or temporary directory:\n\n```sh\nnpm install @openpresentation/opf@X.Y.Z\nnode --input-type=module -e \"import {validatePresentation} from '@openpresentation/opf'; console.log(validatePresentation({name:'t', narrative:'not-a-real-id', slides:[{title:'t'}]}).warnings)\"\n```\n\nThe expected result is one warning about an unknown narratives catalog id.\n\n## GitHub Release Notes\n\nThe core tag workflow creates a GitHub Release from the matching changelog section after publishing. Verify that release after npm is verified. If release creation failed, create the missing release for the existing tag:\n\n```sh\ngh release create opf-vX.Y.Z \\\n --repo OpenPresentation/opf \\\n --title '@openpresentation/opf X.Y.Z' \\\n --notes-file /path/to/release-notes.md\n```\n\nUse the matching `## X.Y.Z` section from `CHANGELOG.md` as the release notes.\n\n## Troubleshooting\n\nIf the tag/version check fails, the tag does not point at the release commit or\nthe tag name does not match `packages/javascript/package.json`. Delete the bad\nlocal and remote tag, fetch `main`, and tag the correct commit:\n\n```sh\ngit push origin :refs/tags/opf-vX.Y.Z\ngit tag -d opf-vX.Y.Z\ngit checkout main\ngit pull origin main\ngit tag opf-vX.Y.Z\ngit push origin opf-vX.Y.Z\n```\n\nIf tests fail, fix the code on `main`, create a new release commit, and move the\ntag only if npm has not already published that version.\n\nIf npm publish fails with `ENEEDAUTH`, confirm:\n\n- npm has a trusted publisher for `OpenPresentation/opf`\n- the trusted publisher uses workflow filename `npm-publish.yml`\n- `.github/workflows/npm-publish.yml` has `id-token: write`\n- the publish job is running on a modern Node/npm toolchain\n\nIf npm publish fails after the version is already present on npm, do not retry\nthe same publish. Verify the package and treat the failure as a duplicate\npublish attempt.\n"
164
218
  },
165
219
  {
166
220
  "slug": "rich-text",
167
221
  "file": "docs/rich-text.md",
168
222
  "title": "Rich text measurement and output",
169
- "markdown": "# Rich text measurement and output\n\nOPF text arrays preserve run formatting in the document. Text payloads now use a shared mixed-style layout for composition, SVG preview, and native PPTX export. Plain string text keeps its existing layout path.\n\nThe shared fit accounts for each run's font family, weight, italic style, and requested point size. Run point sizes are converted to pixels at 96 DPI; default text sizes and composition minimum sizes remain canvas-relative. Fitting can shrink the run sizes together, preserving their relative sizes. Superscripts and subscripts use smaller glyphs and explicit baseline offsets. Line height accounts for the largest ascent/descent. Long tokens wrap at grapheme boundaries; spaces and explicit empty lines are retained.\n\nSVG displays bold, italic, underline, strikethrough, color, font family, size, superscript, subscript, and HTTP(S)/mailto hyperlinks. Other link schemes remain in the OPF source but are not emitted as active links. Use the same loaded font files and measurement provider for SVG and PPTX.\n\nNative PPTX output preserves these run styles and shared wrapping. Each fitted line is an editable text box, which retains measured vertical placement but is not a single continuous PowerPoint paragraph. Arbitrary PPTX import is still not a lossless rich-text round trip. Visual equality across browser and PowerPoint is not established by XML formatting checks.\n\nThe canvas supports native SVG text selection with a formatting toolbar for bold, italic, underline, strikethrough, color, font family, point size, links, and scripts. Double-click a rich text block (or focus it and press Enter) to select its full contents. For a plain text payload, start an inline edit and choose **Format text**. **Selected text** and **Replace text** replace the selected range; **Edit runs** opens structured controls. Each action validates and creates one undo step. A continuous mixed-style typing caret and IME handling remain open work; selection and formatting currently use the rendered SVG itself. List entries and descriptions use the same formatting controls at their own source paths. Complex-script shaping, bidi layout, and font-feature parity remain additional work.\n\n```js\nimport {fitRichText} from '@openpresentation/opf/composition';\nconst fit = fitRichText(\n ['A ', {text:'larger word', fontSize:28, bold:true}],\n {x:0,y:0,width:400,height:200},\n 25, 16,\n {style:{fontFamily:'Roboto',fontWeight:400},textMeasurement},\n);\n// textMeasurement is the host's loaded-font measurement provider.\n// richLines contains positioned fragments, resolved styles, and baselines.\n```\n\nVerification: `node packages/javascript/test/rich-text.mjs`, composition/pagination regressions, and `pnpm test:rich-text`. The latter produces SVG, OPF, and PPTX specimens under `artifacts/rich-text/`. The SVG specimen has been visually inspected in the browser; PPTX verification currently inspects native run XML, not a PowerPoint raster comparison.\n\n## Headless range editing\n\nAny agent or application can use the same immutable helpers, without a browser or AI service:\n\n```js\nimport {formatRichTextRange, replaceRichTextRange} from '@openpresentation/opf-editor/rich-text';\nconst path = 'slides.0.text';\nconst next = formatRichTextRange(editor.get(path), 0, 5, {bold: true});\neditor.set(path, next, {rejectInvalid: true});\n// Other helpers: replaceRichTextRange(value, start, end, replacement), richTextContent(value).\n```\n\nOffsets are UTF-16 offsets, matching DOM Selection. They must fall on whole grapheme boundaries; ranges that split surrogate pairs, combining sequences, or emoji sequences are rejected. Formatting preserves unselected text, run metadata, and links. A `null` style removes an override, while `false` explicitly disables a boolean style. Superscript and subscript are mutually exclusive when applying a new script style. Text replacement inherits the first selected run's style; insertion at a boundary inherits the preceding run. Pass the result through whole-document validation before saving. These APIs are included in coordinated local preview packages; check the installed package exports before assuming registry availability.\n\nBrowser verification: `pnpm demo:editor`, then open `/rich-text-tests.html` on the demo server. The harness covers forward/reverse cross-run selection, shared-renderer source offsets, selection restoration after reflow, styles, links, replacement, undo, stale selection invalidation, plain-text entry, structured controls, and disposal.\n\n## Lists and descriptions\n\n`items` and `bullets` use the shared `fitList` API, including payloads explicitly marked `type: \"text\"` with a `bullets` field. Entries accept strings, run arrays, or objects with `text` and `level`; `items` objects also accept `description`. Rich text is measured without flattening styles. Descriptions default to 82% of the body size. Explicit run point sizes stay absolute until fitting shrinks the whole list uniformly.\n\n```js\nimport {fitList} from '@openpresentation/opf/composition';\nconst fit = fitList([\n {text: ['A ', {text: 'recommendation', bold: true}],\n description: [{text: 'Supporting evidence', italic: true}], level: 1},\n], {x: 0, y: 0, width: 500, height: 300}, 25, 16,\n{style: {fontFamily: 'Roboto', fontWeight: 400, path: 'slides.0.items'}, textMeasurement});\n// listEntries contains text/description boxes, rich lines, markers and source paths.\n```\n\nEach nesting level adds an indent of 1.1 times the fitted body font size. Wrapped lines and descriptions align with the entry text, while character markers cycle through three shapes. Levels are not silently capped at three; excessive indentation reports overflow. Composition scoring and pagination use the measured list height, splitting only between complete entries and preserving descriptions, levels and runs.\n\nThe canvas edits strings inline and rich arrays through selection and formatting. List containers still expose structural properties for adding, removing and reordering entries. Native PPTX uses one editable box per fitted line, with a native bullet only on the first body line. Bullet font, size and color are explicit. PowerPoint paragraph levels stop at eight; deeper OPF levels retain their measured visual offset. Reimport uses heuristics for adjacent bullet boxes and is not a lossless reconstruction of descriptions or rich list structure.\n\nImage bullets and continuous rich typing remain outstanding. Native PowerPoint raster comparison is still needed before claiming pixel parity. Verification: `pnpm test:lists` writes OPF/SVG/PPTX specimens to `artifacts/lists/` and checks native paragraph validity, bullet properties, text and indent coordinates. `/list-tests.html` and its packed-package equivalent cover 19 canvas editing/undo checks.\n"
223
+ "markdown": "# Rich text measurement and output\n\nOPF text arrays preserve run formatting in the document. Text payloads now use a shared mixed-style layout for composition, SVG preview, and native PPTX export. Plain string text keeps its existing layout path.\n\nThe shared fit accounts for each run's font family, weight, italic style, and requested point size. Run point sizes are converted to pixels at 96 DPI; default text sizes and composition minimum sizes remain canvas-relative. Fitting can shrink the run sizes together, preserving their relative sizes. Superscripts and subscripts use smaller glyphs and explicit baseline offsets. Line height accounts for the largest ascent/descent. Long tokens wrap at grapheme boundaries; spaces and explicit empty lines are retained.\n\nSVG displays bold, italic, underline, strikethrough, color, font family, size, superscript, subscript, and HTTP(S)/mailto hyperlinks. Other link schemes remain in the OPF source but are not emitted as active links. Use the same loaded font files and measurement provider for SVG and PPTX.\n\nNative PPTX output preserves these run styles and shared wrapping. Each fitted line is an editable text box, which retains measured vertical placement but is not a single continuous PowerPoint paragraph. Arbitrary PPTX import is still not a lossless rich-text round trip. Visual equality across browser and PowerPoint is not established by XML formatting checks.\n\nThe canvas supports native SVG text selection with a formatting toolbar for bold, italic, underline, strikethrough, color, font family, point size, links, and scripts. Double-click a rich text block (or focus it and press Enter) to select its full contents. For a plain text payload, start an inline edit and choose **Format text**. **Selected text** and **Replace text** replace the selected range; **Edit runs** opens structured controls. Each action validates and creates one undo step. A continuous mixed-style typing caret and IME handling remain open work; selection and formatting currently use the rendered SVG itself. List entries and descriptions use the same formatting controls at their own source paths. Complex-script shaping, bidi layout, and font-feature parity remain additional work.\n\n```js\nimport {fitRichText} from '@openpresentation/opf/composition';\nconst fit = fitRichText(\n ['A ', {text:'larger word', fontSize:28, bold:true}],\n {x:0,y:0,width:400,height:200},\n 25, 16,\n {style:{fontFamily:'Roboto',fontWeight:400},textMeasurement},\n);\n// textMeasurement is the host's loaded-font measurement provider.\n// richLines contains positioned fragments, resolved styles, and baselines.\n```\n\nVerification: `node packages/javascript/test/rich-text.mjs`, composition/pagination regressions, and `pnpm test:rich-text`. The latter produces SVG, OPF, and PPTX specimens under `artifacts/rich-text/`. The SVG specimen has been visually inspected in the browser; PPTX verification currently inspects native run XML, not a PowerPoint raster comparison.\n\n## Headless range editing\n\nAny agent or application can use the same immutable helpers, without a browser or AI service:\n\n```js\nimport {formatRichTextRange, replaceRichTextRange} from '@openpresentation/opf-editor/rich-text';\nconst path = 'slides.0.text';\nconst next = formatRichTextRange(editor.get(path), 0, 5, {bold: true});\neditor.set(path, next, {rejectInvalid: true});\n// Other helpers: replaceRichTextRange(value, start, end, replacement), richTextContent(value).\n```\n\nOffsets are UTF-16 offsets, matching DOM Selection. They must fall on whole grapheme boundaries; ranges that split surrogate pairs, combining sequences, or emoji sequences are rejected. Formatting preserves unselected text, run metadata, and links. A `null` style removes an override, while `false` explicitly disables a boolean style. Superscript and subscript are mutually exclusive when applying a new script style. Text replacement inherits the first selected run's style; insertion at a boundary inherits the preceding run. Pass the result through whole-document validation before saving.\n\nThese helpers are published through `@openpresentation/opf-editor/rich-text` in editor 0.8.0. Use editor 0.8.0 with core 0.11.0, renderer 0.9.0 and PPTX 0.9.1 on Node 24. The [compatibility matrix](compatibility-matrix.md) separates shipped APIs from remaining canvas, font and native Office gates; package availability does not establish arbitrary PPTX round-trip or pixel parity.\n\nBrowser verification: `pnpm demo:editor`, then open `/rich-text-tests.html` on the demo server. The harness covers forward/reverse cross-run selection, shared-renderer source offsets, selection restoration after reflow, styles, links, replacement, undo, stale selection invalidation, plain-text entry, structured controls, and disposal.\n\n## Lists and descriptions\n\n`items` and `bullets` use the shared `fitList` API, including payloads explicitly marked `type: \"text\"` with a `bullets` field. Entries accept strings, run arrays, or objects with `text` and `level`; `items` objects also accept `description`. Rich text is measured without flattening styles. Descriptions default to 82% of the body size. Explicit run point sizes stay absolute until fitting shrinks the whole list uniformly.\n\n```js\nimport {fitList} from '@openpresentation/opf/composition';\nconst fit = fitList([\n {text: ['A ', {text: 'recommendation', bold: true}],\n description: [{text: 'Supporting evidence', italic: true}], level: 1},\n], {x: 0, y: 0, width: 500, height: 300}, 25, 16,\n{style: {fontFamily: 'Roboto', fontWeight: 400, path: 'slides.0.items'}, textMeasurement});\n// listEntries contains text/description boxes, rich lines, markers and source paths.\n```\n\nEach nesting level adds an indent of 1.1 times the fitted body font size. Wrapped lines and descriptions align with the entry text, while character markers cycle through three shapes. Levels are not silently capped at three; excessive indentation reports overflow. Composition scoring and pagination use the measured list height, splitting only between complete entries and preserving descriptions, levels and runs.\n\nThe canvas edits strings inline and rich arrays through selection and formatting. List containers still expose structural properties for adding, removing and reordering entries. Native PPTX uses one editable box per fitted line, with a native bullet only on the first body line. Bullet font, size and color are explicit. PowerPoint paragraph levels stop at eight; deeper OPF levels retain their measured visual offset. Reimport uses heuristics for adjacent bullet boxes and is not a lossless reconstruction of descriptions or rich list structure.\n\nImage bullets and continuous rich typing remain outstanding. Native PowerPoint raster comparison is still needed before claiming pixel parity. Verification: `pnpm test:lists` writes OPF/SVG/PPTX specimens to `artifacts/lists/` and checks native paragraph validity, bullet properties, text and indent coordinates. `/list-tests.html` and its packed-package equivalent cover 19 canvas editing/undo checks.\n"
170
224
  },
171
225
  {
172
226
  "slug": "schema-reference",
173
227
  "file": "docs/schema-reference.md",
174
228
  "title": "OPF Presentation Schema Reference",
175
- "markdown": "# OPF Presentation Schema Reference\n\nThis reference documents the author-facing shape of a complete `*.opf.json` presentation document. It summarizes the canonical schema in `spec/schemas/opf.schema.json`; the schema remains the source of truth for validators.\n\n## Document Contract\n\n- Schema id: `https://openpresentation.org/schema/opf/v1`\n- Required top-level fields: `slides`\n- Additional top-level fields: not allowed\n\n## Top-Level Fields\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | no | `const:\"https://openpresentation.org/schema/opf/v1\"` | Optional OPF schema version. When omitted, validators and engines should assume the latest supported OPF schema. |\n| `name` | no | `string` | Display name of the presentation for GUI/TUI lists, library/search indexing, OS-level metadata, and default export filenames. This is deck identity, not slide content. Use slides[].title and slides[].subtitle for text... |\n| `description` | no | `string` | Free-form prose describing what this presentation is about. Used by agents and humans as a deck-level summary; complements purpose (the goal) and narrative (the structured storyline). Round-trips to OOXML 'docProps/co... |\n| `filename` | no | `string` | Optional base filename for exports (without extension). Engine strips a trailing .pptx, .pdf, .png, or .svg (case-insensitive) and appends the target format's extension. When omitted, the engine slugifies name when pr... |\n| `organization` | no | `oneOf:ref:Organization / array<ref:Organization>` | Organization associated with the presentation, usually the presenting company. Array form supports hosts, partners, clients, and sponsors. The primary organization (declared via Organization.role or, if no role is set... |\n| `speaker` | no | `oneOf:ref:Speaker / array<ref:Speaker>` | Person presenting the deck. Array form supports panels and multi-speaker decks. Used for cover slides, bio slides, footers, and panel attribution. |\n| `author` | no | `oneOf:string / array<string>` | Optional credit for the person who authored or contributed to the deck, distinct from speaker. Array form supports multiple contributors. Round-trips to OOXML 'docProps/core.xml' as '<dc:creator>' (semicolon-joined wh... |\n| `audience` | no | `oneOf:string / array<oneOf:string / ref:Audience>` | Intended audiences for the presentation. Accepts either: - A single string shorthand: free-form description ('Series B investors'), an audiences catalog id ('executives'), an HTTPS URL, or a 'pkg:' reference. - An arr... |\n| `purpose` | no | `oneOf:string / ref:Purpose` | Primary goal of the presentation. Accepts either: - A string shorthand: free-form goal ('Raise a Series B round of $30M'), a purposes catalog id ('decide', 'align'), an HTTPS URL, or a 'pkg:' reference. - An inline Pu... |\n| `language` | no | `oneOf:string / ref:Language` | Language for the presentation content. Accepts either: - A string shorthand: a BCP-47 language tag ('en-US', 'en-GB', 'ja-JP', 'fr'), a languages catalog id ('english', 'japanese'), an HTTPS URL, or a 'pkg:' reference... |\n| `tone` | no | `oneOf:string / ref:Tone` | Desired tone for the presentation. Accepts either: - A string shorthand: a tones catalog id ('formal'), an HTTPS URL, or a 'pkg:' reference. - An inline Tone object for custom tone metadata or catalog-backed overrides... |\n| `takeaway` | no | `oneOf:string / array<string>` | Audience-facing takeaway the presentation should leave behind. Array form supports multiple takeaways. Deck-level intent used by AI to seed and pressure-test slide content. |\n| `duration` | no | `integer` | Target presentation duration, as an integer number of minutes. Used by AI to set pace and depth, and to compare against the resolved narrative's durationRange. |\n| `tags` | no | `array<string>` | Free-form labels used for categorization, search, and filtering. Lowercase kebab-case is recommended for consistency across a deck library. |\n| `design` | no | `ref:Design` | Optional design system covering theme, color scheme, font scheme, dimensions, background, logo, watermark, header, and footer applied to the deck. When omitted, engines use their default design configuration. |\n| `narrative` | no | `oneOf:string / ref:Narrative` | Structured storyline describing the deck's arc and beats. Resolves to the 'id' of a 'narratives' catalog record. Accepts two forms: - String shorthand for the common case: 'narrative = \"classic-story\"'. Accepts a bare... |\n| `slides` | yes | `array<ref:Slide>` | Ordered array of slides that make up the presentation. |\n| `assets` | no | `ref:Assets` | Optional reusable asset registry for images, data files, videos, documents, fonts, and other resources referenced elsewhere in the deck via 'asset:<id>' strings. |\n| `catalogs` | no | `ref:Catalogs` | Optional per-kind catalog overrides. Each kind may declare a non-default 'source' and/or inline 'records' that override or supplement the default catalog at https://www.pptx.gallery/<kind>. References elsewhere in the... |\n| `extensions` | no | `object` | Custom data passthrough for agent workflows; ignored by the engine but preserved across read/write round-trips. |\n\n## Object And Type Reference\n\n### Assets\n\n- Type: `object`\n- Required fields: none\n- Purpose: Reusable asset registry for resources used by slides, charts, metadata, and design. Keys are stable asset ids referenced elsewhere as 'asset:<id>'. Each asset can be a source string or an object with src plus optional metadata.\n\n_No named properties._\n\n\n### Asset\n\n- Type: `oneOf:string / object`\n- Required fields: none\n- Purpose: Reusable or inline resource. A string is shorthand for { \"src\": value }. Source strings accept 'asset:<id>' references, HTTPS URLs, data URIs, relative paths resolved against the OPF file location, or local filesystem paths. Use object form when metadata such as alt text, title, mediaType, or format matters.\n\n_No named properties._\n\n\n### Audience\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline audience metadata for the presentation. Use 'id' to reference an audiences catalog record and override selected fields, or use 'name' for a custom inline audience.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional audiences catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable audience name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the audience. |\n| `description` | no | `string` | Longer prose describing the audience and how to address them. |\n| `seniority` | no | `enum:ic \\| manager \\| director \\| vp \\| c-suite \\| mixed` | Typical seniority level of the audience. |\n| `technicalFluency` | no | `enum:low \\| medium \\| high \\| mixed` | Typical technical fluency of the audience. |\n| `decisionPower` | no | `enum:informational \\| advisory \\| decision-maker` | Whether the audience is expected to be informed, advise, or decide. |\n| `attentionBudgetMinutes` | no | `number` | Realistic upper bound on focused attention for a single presentation, in minutes. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this audience. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this audience. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Purpose\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline purpose metadata for the presentation. Use 'id' to reference a purposes catalog record and override selected fields, or use 'name' for a custom inline purpose.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional purposes catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable purpose name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the purpose. |\n| `description` | no | `string` | Longer prose describing when to use this purpose and how it should shape a deck. |\n| `outcome` | no | `string` | Desired audience outcome after the presentation. |\n| `successCriteria` | no | `array<string>` | Observable signals that the deck accomplished this purpose. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this purpose. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this purpose. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Language\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline language metadata for the presentation. Use 'id' to reference a languages catalog record and override selected fields, or use 'bcp47' for a custom language tag without a catalog record.\n- Conditional requirement: `id` or `bcp47`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional languages catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable language name. |\n| `bcp47` | no | `string` | BCP-47 language tag used for locale-aware rendering, proofing, and accessibility metadata. Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region form. |\n| `code` | no | `string` | ISO 639-3 or 639-2 language code carried for engines that prefer ISO codes. |\n| `direction` | no | `enum:ltr \\| rtl` | Base text direction for the language. |\n| `script` | no | `string` | ISO 15924 script code when the writing system should be explicit. |\n| `fontScheme` | no | `string` | Default font-scheme id for this language when targeting PowerPoint output. |\n| `googleFontScheme` | no | `string` | Default font-scheme id for this language when targeting Google Slides output. |\n| `summary` | no | `string` | One-sentence note about coverage or font defaults. |\n| `description` | no | `string` | Longer prose describing the language record and any font-pairing rationale. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Tone\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline tone metadata for the presentation. Use 'id' to reference a tones catalog record and override selected fields, or use 'name' for a custom inline tone.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional tones catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable tone name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the tone. |\n| `description` | no | `string` | Longer prose describing the tone and the kinds of decks it suits. |\n| `voiceCues` | no | `array<string>` | Short directives that shape AI generation toward this tone. |\n| `avoid` | no | `array<string>` | Anti-patterns that AI generation should not produce when this tone is active. |\n| `samplePhrases` | no | `array<string>` | Short example phrases that exemplify this tone. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids this tone pairs well with. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Organization\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: An organization associated with the presentation typically the presenting company, but also hosts, partners, clients, or sponsors. Surfaced on cover slides, footers, and brand bars; the primary organization's logo is the default deck logo unless overridden by design.logo.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier for the organization, used to reference it from Speaker.organizationId. Must be unique within the deck. |\n| `name` | yes | `string` | Display name shown on slides. |\n| `legalName` | no | `string` | Optional legal entity name when it differs from the display name. |\n| `logo` | no | `ref:Asset` | Source for the organization's logo image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are SVG (preferred for vector log... |\n| `domain` | no | `string` | Bare internet domain for the organization. Used for footers, contact slides, and engine-driven asset lookups (e.g., favicon-based brand defaults). |\n| `email` | no | `string` | General contact email for the organization. Used on contact slides and footer attribution. |\n| `phone` | no | `string` | Main contact phone number for the organization. E.164 format is recommended. |\n| `tagline` | no | `string` | Short tagline rendered alongside the organization name on cover slides. |\n| `role` | no | `enum:primary \\| partner \\| client \\| sponsor \\| host` | Role of the organization relative to the presentation. When omitted, the single organization or first organization in array form is treated as primary. |\n| `socials` | no | `ref:Socials` | Optional social media handles or URLs for the organization. |\n\n\n### Speaker\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A person presenting the deck. Used for cover slides, bio/intro slides, footer attribution, and panel formats with multiple presenters.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier for the speaker, used for cross-references within the deck. Must be unique within the deck. |\n| `name` | yes | `string` | Display name. |\n| `title` | no | `string` | Role or title. Often paired with the speaker's organization on cover slides. |\n| `photo` | no | `ref:Asset` | Source for the speaker's headshot image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are JPG or PNG; SVG is not appropr... |\n| `email` | no | `string` | Contact email, used on contact slides or footer attribution when appropriate. |\n| `phone` | no | `string` | Contact phone number for the speaker. E.164 format is recommended. |\n| `bio` | no | `string` | Short biographical paragraph for bio or 'about the speaker' slides. |\n| `organizationId` | no | `string` | Reference to an Organization.id in organization. Lets a speaker be attributed to their org in panel or multi-org decks without repeating organization details. |\n| `socials` | no | `ref:Socials` | Optional social media handles or URLs for the speaker. |\n\n\n### Socials\n\n- Type: `object`\n- Required fields: none\n- Purpose: Social media handles or URLs, keyed by platform id from the 'socialPlatforms' catalog. Each value is a string either a full URL or a platform handle (e.g., '@acme'). The catalog record for each platform carries the URL pattern, handle prefix, brand color, and themed icons used by renderers. Keys resolve to the 'id' of a 'socialPlatforms' catalog record. Resolution order: inline catalogs.socialPlatforms.records[] catalogs.socialPlatforms.source default catalog at https://www.pptx.gallery/socia...\n\n_No named properties._\n\n\n### Narrative\n\n- Type: `object`\n- Required fields: none\n- Purpose: Structured storyline used by AI to shape generated content. Mirrors the OPF Narrative Template record at https://openpresentation.org/schema/opf-narrative/v1 (sans '$schema'), so a library record and an inline narrative are interchangeable. Narrative declares the deck's intended story arc; slides may opt into beats via Slide.beat. The narrative does not constrain slide structure validators warn on drift (orphan slides, unused beats) but never error. Slides are the source of truth; narrative i...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Stable slug identifying this narrative. When it matches a record in the resolved 'narratives' catalog, the catalog record's beats and metadata seed this narrative; inline fields override per-key. When it doesn't match... |\n| `name` | no | `string` | Human-readable narrative name. |\n| `summary` | no | `string` | One-sentence description of when and why to use this narrative. |\n| `description` | no | `string` | Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction. |\n| `audienceFit` | no | `array<string>` | Audiences this narrative works well for. Free-form strings or 'audiences' catalog ids. |\n| `durationRange` | no | `object` | Typical talk-length window this narrative suits. Compared by validators against duration. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the narrative, used by picker UIs and inline rendering. All sub-fields are optional. |\n| `beats` | no | `array<ref:NarrativeBeat>` | Ordered list of beats that make up the narrative arc. When 'id' matches a catalog record, beats here override or extend matching catalog beats by their own 'id'. Beat IDs must be unique within the narrative. |\n\n\n### NarrativeBeat\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A single narrative beat a labeled segment of the story arc with a specific dramatic purpose (e.g. 'hook', 'problem', 'evidence', 'ask'). Slides reference beats via Slide.beat. Beats may also carry slide-blueprint hints (slideType, layoutHint, thoughtCues, instructions) that guide the assigned slide. Mirrors the Beat definition in narrative.schema.json (https://openpresentation.org/schema/opf-narrative/v1) so library entries and inline OPF beats are interchangeable.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable beat name. |\n| `description` | no | `string` | Curator-written prose that explains what this beat should accomplish. |\n| `instructions` | no | `string` | Short author-facing instruction for the beat typically one phrase. Complements 'description' with a concise directive. |\n| `slideCount` | no | `integer` | Optional explicit slide count for this beat. Defaults to 1 when omitted; values >1 are reserved for beats that intentionally span multiple slides. Prefer decomposing a heavy beat into multiple beats over setting a hig... |\n| `slideType` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Default content kind for the beat's slide. Mirrors ContentPayload.type and helps engines choose a sensible layout when only the beat is specified. |\n| `layoutHint` | no | `string` | Suggested layout id for the beat's opening slide. Resolves the same way as Slide.layout against catalogs.layouts and the default catalog at https://www.pptx.gallery/layouts. |\n| `thoughtCues` | no | `array<string>` | Optional speaker or thinking cues attached to the beat. Surfaced in presenter notes. |\n\n\n### Design\n\n- Type: `object`\n- Required fields: none\n- Purpose: Visual design system applied to the presentation; individual slides may override fields via Slide.design.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `theme` | no | `oneOf:string / ref:Theme` | Theme for the deck. Accepts two forms: - String shorthand: 'design.theme = \"minimal\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'themes' catalog record. - Object form: a Theme with an optional... |\n| `colorScheme` | no | `oneOf:string / ref:ColorScheme` | Color scheme for the presentation. Accepts two forms: - String shorthand: 'design.colorScheme = \"cool-horizon\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'colorSchemes' catalog record. - Objec... |\n| `fontScheme` | no | `oneOf:string / ref:FontScheme` | Font scheme for heading, body, accent, and code text. Accepts two forms: - String shorthand: 'design.fontScheme = \"aptos\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'fontSchemes' catalog recor... |\n| `dimensions` | no | `oneOf:ref:DimensionPreset / ref:Dimensions` | Slide dimensions and aspect ratio. String shorthand such as 'widescreen' is equivalent to { preset: 'widescreen' }. |\n| `background` | no | `oneOf:ref:BackgroundShortcut / ref:Background` | Default slide background applied across the deck unless overridden on a slide. String shorthand accepts theme slots ('light1', 'light2', 'dark1', 'dark2') or hex colors; object forms support theme, solid, gradient, im... |\n| `logo` | no | `oneOf:ref:Asset / ref:LogoSet` | Deck logo assets used by layouts, covers, section dividers, headers, and footers. A string is the default logo source; object form provides light/dark, stacked, icon, and wordmark variants. When omitted, the renderer... |\n| `watermark` | no | `oneOf:const:false / ref:Asset / ref:Watermark` | Optional decorative watermark applied across slides. Use false to suppress an inherited watermark in slide-level design; a string is equivalent to { src: value }. |\n| `header` | no | `oneOf:const:false / ref:HeaderFooter` | Repeated header furniture rendered outside the main slide content. Use false to suppress an inherited header. |\n| `footer` | no | `oneOf:const:false / ref:HeaderFooter` | Repeated footer furniture rendered outside the main slide content. Use false to suppress an inherited footer. |\n| `titleAlignment` | no | `enum:left \\| center \\| right` | Default horizontal alignment for title placeholders in resolved layouts. |\n| `contentAlignment` | no | `enum:left \\| center \\| right` | Default horizontal alignment for body/content regions in resolved layouts. |\n| `contentBox` | no | `boolean` | Whether body/content regions are rendered inside a visible card or surface. |\n| `slideImage` | no | `oneOf:ref:Asset / object` | Optional slide-level image treatment used by layouts that support a decorative or editorial image separate from content images. |\n| `contentDirection` | no | `enum:horizontal \\| vertical` | Axis along which parallel body/content regions are arranged. |\n| `chartPrimary` | no | `enum:none \\| top \\| bottom \\| left \\| right` | For chart layouts, where the primary chart sits relative to supporting content. 'none' means chart regions have equal weight. |\n| `imageFill` | no | `enum:crop \\| fit` | How picture placeholders fill their allocated region. |\n| `listBullet` | no | `enum:character \\| image` | Default bullet rendering style for list layouts. |\n\n\n### Theme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Theme bundle used by the design system. In design.theme, 'id' resolves a themes catalog record as the base; any sibling fields override the resolved theme. The string shorthand on design.theme is equivalent to setting only 'id'.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Theme reference. Resolves to the 'id' of a 'themes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'minimal'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Field overrides on the surro... |\n| `name` | no | `string` | Human-readable theme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the theme - when to reach for it. |\n| `description` | no | `string` | Longer prose describing what the theme looks and feels like and the kinds of decks it suits. |\n| `colorScheme` | no | `oneOf:string / ref:ColorScheme` | Default color scheme for this theme. A string resolves against catalogs.colorSchemes; an object may provide an 'id' base reference plus overrides. |\n| `fontScheme` | no | `oneOf:string / ref:FontScheme` | Default font scheme for this theme. A string resolves against catalogs.fontSchemes; an object may provide an 'id' base reference plus overrides. |\n| `background` | no | `oneOf:ref:BackgroundShortcut / ref:Background` | Default background for this theme. String shorthand accepts theme slots ('light1', 'light2', 'dark1', 'dark2') or hex colors. |\n| `dimensions` | no | `oneOf:ref:DimensionPreset / ref:Dimensions` | Default slide size for this theme. A string preset is equivalent to { preset: value }. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### ColorScheme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Color palette used by the design system. The slot fields (accent1-accent6, dark1, dark2, light1, light2, hyperlink, followedHyperlink) mirror color-scheme.schema.json (https://openpresentation.org/schema/opf-color-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields. Two parallel models are supported and may be mixed: - OOXML slots - the 12-slot PowerPoint theme model that round-trips directly to OOXML. Use these for full control over the palette as Power...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Color scheme reference. Resolves to the 'id' of a 'colorSchemes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'cool-horizon'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Slot and r... |\n| `accent1` | no | `string` | Accent 1 color (hex). Mirrors the OOXML accent1 slot. |\n| `accent2` | no | `string` | Accent 2 color (hex). Mirrors the OOXML accent2 slot. |\n| `accent3` | no | `string` | Accent 3 color (hex). Mirrors the OOXML accent3 slot. |\n| `accent4` | no | `string` | Accent 4 color (hex). Mirrors the OOXML accent4 slot. |\n| `accent5` | no | `string` | Accent 5 color (hex). Mirrors the OOXML accent5 slot. |\n| `accent6` | no | `string` | Accent 6 color (hex). Mirrors the OOXML accent6 slot. |\n| `dark1` | no | `string` | Dark 1 color (hex). Typically the deepest neutral; OOXML dark1. |\n| `dark2` | no | `string` | Dark 2 color (hex). Secondary dark; OOXML dark2. |\n| `light1` | no | `string` | Light 1 color (hex). Typically the slide canvas; OOXML lt1. |\n| `light2` | no | `string` | Light 2 color (hex). Secondary light surface; OOXML lt2. |\n| `hyperlink` | no | `string` | Hyperlink color (hex). OOXML hlink. |\n| `followedHyperlink` | no | `string` | Followed-hyperlink color (hex). OOXML folHlink. |\n| `primary` | no | `string` | Abstract role: primary brand color (hex). The engine maps this onto an OOXML accent slot when serializing. |\n| `secondary` | no | `string` | Abstract role: secondary brand color (hex). |\n| `accent` | no | `string` | Abstract role: accent color used for highlights and emphasis (hex). |\n| `background` | no | `string` | Abstract role: default slide background color (hex). The engine maps this to one of light1 / light2 / dark1 / dark2 when serializing. |\n| `surface` | no | `string` | Abstract role: color for elevated surfaces such as cards and panels (hex). |\n| `text` | no | `string` | Abstract role: primary body text color (hex). |\n| `textSecondary` | no | `string` | Abstract role: secondary or muted text color used for captions and supporting copy (hex). |\n| `custom` | no | `object` | Map of custom named colors for advanced or theme-specific use. |\n\n\n### FontScheme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Typography selections used by the design system. The pair fields (major, minor) and refinement fields (type, app, languageFamily) mirror font-scheme.schema.json (https://openpresentation.org/schema/opf-font-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields. Two parallel models are supported and may be mixed: - OOXML pairs (major, minor) - heading and body family names that round-trip directly to PowerPoint majorFont/minorFont entries. - Abstract roles...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Font scheme reference. Resolves to the 'id' of a 'fontSchemes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'aptos'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Field overrides on... |\n| `major` | no | `string` | Heading (major) font family mirrors the OOXML majorFont entry. Pairs with 'minor'. |\n| `minor` | no | `string` | Body (minor) font family mirrors the OOXML minorFont entry. Pairs with 'major'. |\n| `type` | no | `enum:sans-serif \\| serif \\| monospace` | High-level typographic class of the scheme. |\n| `app` | no | `enum:PowerPoint \\| Google Slides` | Target application this font pairing is intended for. |\n| `languageFamily` | no | `enum:latin \\| ea \\| cs` | OOXML font-language family this scheme is intended for: 'latin' for Latin-script content, 'ea' for East Asian scripts, 'cs' for Complex Scripts. |\n| `heading` | no | `ref:Font` | Abstract role: font used for slide titles and headings. Maps onto the OOXML major slot when serializing. |\n| `body` | no | `ref:Font` | Abstract role: font used for body copy. Maps onto the OOXML minor slot when serializing. |\n| `accent` | no | `ref:Font` | Abstract role: font used for accent text such as quotes or callouts. No direct OOXML slot. |\n| `code` | no | `ref:Font` | Abstract role: monospaced font used for code blocks. No direct OOXML slot. |\n\n\n### Font\n\n- Type: `object`\n- Required fields: `family`\n- Purpose: Specification for a single font role.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `family` | yes | `string` | Font family name. |\n| `weight` | no | `number` | Numeric font weight (e.g., 400 for regular, 700 for bold). |\n| `style` | no | `enum:normal \\| italic` | Font style. |\n| `letterSpacing` | no | `number` | Letter spacing (tracking) in ems. |\n\n\n### DimensionPreset\n\n- Type: `enum:16:9 | 4:3 | 16:10 | letter | a4 | widescreen | standard`\n- Required fields: none\n- Purpose: Named dimension preset; chooses both aspect ratio and physical size. 'widescreen' is an alias for 16:9 in PowerPoint widescreen size; 'standard' is an alias for 4:3 in PowerPoint standard size.\n\n_No named properties._\n\n\n### Dimensions\n\n- Type: `object`\n- Required fields: none\n- Purpose: Slide dimensions; either pick a preset or specify custom inches.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `preset` | no | `ref:DimensionPreset` | |\n| `widthInches` | no | `number` | Custom slide width in inches; overrides the preset width when provided. |\n| `heightInches` | no | `number` | Custom slide height in inches; overrides the preset height when provided. |\n\n\n### ThemeBackgroundSlot\n\n- Type: `enum:light1 | light2 | dark1 | dark2`\n- Required fields: none\n- Purpose: PowerPoint theme-controlled slide background slot from the active color scheme. These are slots, not assumptions about actual colors: light1 is usually white and dark1 is usually black by convention, but the color scheme controls the real values.\n\n_No named properties._\n\n\n### HexColor\n\n- Type: `string`\n- Required fields: none\n- Purpose: Hex color shorthand accepted by selected string fields.\n\n_No named properties._\n\n\n### BackgroundShortcut\n\n- Type: `oneOf:ref:ThemeBackgroundSlot / ref:HexColor`\n- Required fields: none\n- Purpose: String shorthand for a background. Theme slots ('light1', 'light2', 'dark1', 'dark2') are equivalent to { type: 'theme', slot: value }; hex colors are equivalent to { type: 'solid', color: value }.\n\n_No named properties._\n\n\n### Background\n\n- Type: `oneOf:ref:ThemeBackground / ref:SolidBackground / ref:GradientBackground / ref:ImageBackground / ref:PatternBackground`\n- Required fields: none\n- Purpose: Background fill applied to slides. Theme backgrounds preserve PowerPoint's color-scheme background choice; other variants represent fixed background fills.\n\n_No named properties._\n\n\n### ThemeBackground\n\n- Type: `object`\n- Required fields: `type`, `slot`\n- Purpose: Theme-controlled PowerPoint slide background. The slot is resolved through the active color scheme and remains theme-aware.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"theme\"` | Theme-controlled background fill. |\n| `slot` | yes | `ref:ThemeBackgroundSlot` | |\n\n\n### SolidBackground\n\n- Type: `object`\n- Required fields: `type`, `color`\n- Purpose: Fixed solid slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"solid\"` | Fixed solid background fill. |\n| `color` | yes | `string` | Fixed solid fill color, usually a hex string. Use { type: 'theme', slot: ... } for PowerPoint's four theme-controlled background choices. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### GradientBackground\n\n- Type: `object`\n- Required fields: `type`, `gradient`\n- Purpose: Fixed gradient slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"gradient\"` | Fixed gradient background fill. |\n| `gradient` | yes | `object` | Gradient fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### ImageBackground\n\n- Type: `object`\n- Required fields: `type`, `image`\n- Purpose: Fixed image slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"image\"` | Fixed image background fill. |\n| `image` | yes | `object` | Image fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### PatternBackground\n\n- Type: `object`\n- Required fields: `type`, `pattern`\n- Purpose: Fixed pattern slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"pattern\"` | Fixed pattern background fill. |\n| `pattern` | yes | `object` | Pattern fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### LogoSet\n\n- Type: `object`\n- Required fields: none\n- Purpose: Deck logo variants surfaced by layouts, covers, section dividers, headers, and footers. Organization identity lives in organization; this object only controls visual rendering assets. Renderer convention: on dark backgrounds prefer the 'light' variant, on light backgrounds prefer the 'dark' variant, and in square/vertical slots prefer the stacked family when present.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `default` | no | `ref:Asset` | Default full-lockup logo. Used as fallback when no more specific variant is set. |\n| `light` | no | `ref:Asset` | Light-colored full-lockup logo intended for rendering on dark backgrounds. |\n| `dark` | no | `ref:Asset` | Dark-colored full-lockup logo intended for rendering on light backgrounds. |\n| `stacked` | no | `ref:Asset` | Stacked vertical logo lockup, suited to portrait or square brand-mark slots. |\n| `stackedLight` | no | `ref:Asset` | Light-colored stacked logo variant intended for rendering on dark backgrounds. |\n| `stackedDark` | no | `ref:Asset` | Dark-colored stacked logo variant intended for rendering on light backgrounds. |\n| `icon` | no | `ref:Asset` | Default icon, mark, or symbol without wordmark. Useful for tight spaces such as footers, badges, and slide-corner marks. |\n| `iconLight` | no | `ref:Asset` | Light-colored icon variant intended for rendering on dark backgrounds. |\n| `iconDark` | no | `ref:Asset` | Dark-colored icon variant intended for rendering on light backgrounds. |\n| `wordmark` | no | `ref:Asset` | Default wordmark: the organization name set in branded typography, without icon. |\n| `wordmarkLight` | no | `ref:Asset` | Light-colored wordmark variant intended for rendering on dark backgrounds. |\n| `wordmarkDark` | no | `ref:Asset` | Dark-colored wordmark variant intended for rendering on light backgrounds. |\n\n\n### Watermark\n\n- Type: `object`\n- Required fields: `opacity`\n- Purpose: Decorative watermark image and rendering options. Use design.watermark = false to disable an inherited watermark.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `src` | no | `string` | Source for the watermark image. |\n| `opacity` | yes | `number` | Watermark opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### HeaderFooter\n\n- Type: `object`\n- Required fields: none\n- Purpose: Repeated header or footer content split into left, center, and right zones. Header/footer content is slide furniture, separate from the main slide content payloads.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `left` | no | `ref:HeaderFooterItem` | Left-aligned header/footer content. |\n| `center` | no | `ref:HeaderFooterItem` | Centered header/footer content. |\n| `right` | no | `ref:HeaderFooterItem` | Right-aligned header/footer content. |\n\n\n### HeaderFooterItem\n\n- Type: `object`\n- Required fields: none\n- Purpose: One header/footer zone. Fields may be combined when the renderer supports it; otherwise renderers should prefer image, then text-like generated content.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `text` | no | `string` | Literal text rendered in this zone. |\n| `image` | no | `ref:Asset` | Generic image rendered in this zone, such as a logo, partner mark, certification badge, or icon. |\n| `slideNumber` | no | `boolean` | Whether to render the current slide number in this zone. |\n| `date` | no | `oneOf:boolean / string` | Whether to render the presentation date, or a literal date string to render. |\n| `organization` | no | `boolean` | Whether to render the primary organization name from organization. |\n| `section` | no | `boolean` | Whether to render the current slide section label. |\n\n\n### Slide\n\n- Type: `object`\n- Required fields: none\n- Purpose: A single slide. Content can be authored as a full-slide root payload, or inside promoted named region keys such as 'left', 'center+right', and 'top:left'.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional stable identifier for the slide within the document. Use when another system needs to reference a slide across edits, comments, generation state, exports, or narrative tooling. Slide order is defined by the s... |\n| `type` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Optional full-slide content kind. When omitted, engines infer the kind from root payload fields. |\n| `beat` | no | `oneOf:string / array<string>` | Optional reference to one or more narrative beats (each value matches an id from narrative.beats or the resolved template). A single string declares the slide's primary beat; an array declares that one slide covers mu... |\n| `layout` | no | `string` | Optional slide layout reference. Resolves to the 'id' of a 'layouts' catalog record. When omitted, engines infer a layout from the slide's root payload or promoted region keys. Accepts a bare id (lowercase kebab-case,... |\n| `title` | no | `string` | Slide-level title content. When the resolved layout exposes a 'title' placeholder, the engine renders this value there. |\n| `subtitle` | no | `string` | Slide-level subtitle or supporting line. When the resolved layout exposes a 'subtitle' placeholder, the engine renders this value there. |\n| `tag` | no | `string` | Small slide-level label or badge. When the resolved layout exposes a 'tag' placeholder, the engine renders this value there. |\n| `text` | no | `oneOf:string / array<ref:TextRun>` | Full-slide text payload. Use a string for plain text or TextRun[] for inline rich text. TextRun items may be plain strings or formatted run objects. |\n| `items` | no | `array<ref:ListItem>` | Full-slide generic list payload. Presence of this field infers type 'list'. At slide root, multiple content payload kinds with no explicit type, blocks, or regions are accepted as shorthand for layout-agnostic blocks. |\n| `bullets` | no | `array<ref:BulletItem>` | Full-slide text-style bullet payload. Presence of this field infers type 'text'. |\n| `image` | no | `ref:Asset` | Full-slide image source. Presence of this field infers type 'image'. |\n| `video` | no | `ref:Asset` | Full-slide video source. Presence of this field infers type 'video'. |\n| `chart` | no | `ref:Chart` | Full-slide chart payload. Presence of this field infers type 'chart'. |\n| `table` | no | `ref:Table` | Full-slide table payload. Presence of this field infers type 'table'. |\n| `code` | no | `oneOf:string / ref:Code` | Full-slide code payload. A string is shorthand for { \"source\": value }; object form carries optional syntax language and filename metadata. |\n| `metric` | no | `oneOf:string / number / ref:Metric` | Full-slide metric payload. A string or number is shorthand for { \"value\": value }; object form carries optional label, description, unit, delta, and trend metadata. Numeric values remain numbers; renderers format them... |\n| `quote` | no | `oneOf:string / ref:Quote` | Full-slide quote payload. A string is shorthand for { \"text\": value }; object form carries optional attribution and source metadata. Presence of this field infers type 'quote'. |\n| `timeline` | no | `ref:Timeline` | Full-slide timeline payload. An array is shorthand for { \"events\": value }; object form carries optional name and description metadata. Presence of this field infers type 'timeline'. |\n| `blocks` | no | `array<ref:ContentPayload>` | Layout-agnostic content blocks rendered together as a composed payload when exact placement is unspecified. At slide root, multiple content payload kinds with no explicit type, blocks, or regions are accepted as short... |\n| `design` | no | `ref:Design` | Slide-level design applied on top of the deck-wide design. |\n| `left` | no | `ref:ContentPayload` | |\n| `center` | no | `ref:ContentPayload` | |\n| `right` | no | `ref:ContentPayload` | |\n| `left+center` | no | `ref:ContentPayload` | |\n| `center+right` | no | `ref:ContentPayload` | |\n| `left+center+right` | no | `ref:ContentPayload` | |\n| `top` | no | `ref:ContentPayload` | |\n| `middle` | no | `ref:ContentPayload` | |\n| `bottom` | no | `ref:ContentPayload` | |\n| `top+middle` | no | `ref:ContentPayload` | |\n| `middle+bottom` | no | `ref:ContentPayload` | |\n| `top+middle+bottom` | no | `ref:ContentPayload` | |\n| `top:left` | no | `ref:ContentPayload` | |\n| `top:center` | no | `ref:ContentPayload` | |\n| `top:right` | no | `ref:ContentPayload` | |\n| `top:left+center` | no | `ref:ContentPayload` | |\n| `top:center+right` | no | `ref:ContentPayload` | |\n| `top:left+center+right` | no | `ref:ContentPayload` | |\n| `middle:left` | no | `ref:ContentPayload` | |\n| `middle:center` | no | `ref:ContentPayload` | |\n| `middle:right` | no | `ref:ContentPayload` | |\n| `middle:left+center` | no | `ref:ContentPayload` | |\n| `middle:center+right` | no | `ref:ContentPayload` | |\n| `middle:left+center+right` | no | `ref:ContentPayload` | |\n| `bottom:left` | no | `ref:ContentPayload` | |\n| `bottom:center` | no | `ref:ContentPayload` | |\n| `bottom:right` | no | `ref:ContentPayload` | |\n| `bottom:left+center` | no | `ref:ContentPayload` | |\n| `bottom:center+right` | no | `ref:ContentPayload` | |\n| `bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `top+middle:left` | no | `ref:ContentPayload` | |\n| `top+middle:center` | no | `ref:ContentPayload` | |\n| `top+middle:right` | no | `ref:ContentPayload` | |\n| `top+middle:left+center` | no | `ref:ContentPayload` | |\n| `top+middle:center+right` | no | `ref:ContentPayload` | |\n| `top+middle:left+center+right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left` | no | `ref:ContentPayload` | |\n| `middle+bottom:center` | no | `ref:ContentPayload` | |\n| `middle+bottom:right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left+center` | no | `ref:ContentPayload` | |\n| `middle+bottom:center+right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:center` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left+center` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:center+right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `notes` | no | `string` | Speaker notes shown in presenter view. |\n| `section` | no | `string` | PowerPoint-style slide section label. Consecutive slides with the same value belong to the same section in presenter view, outlines, and PowerPoint section-aware exports. |\n| `hidden` | no | `boolean` | Whether the slide is hidden from the presented sequence. |\n| `composition` | no | `ref:Composition` | |\n\n\n### ContentPayload\n\n- Type: `allOf:schema + schema + schema + schema + schema + schema + schema + schema + schema + schema + schema + schema`\n- Required fields: none\n- Purpose: A content leaf or recursively composed group. A group contains blocks and optional composition; it cannot mix blocks with leaf payload fields. Groups may nest up to 32 levels.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline \\| group` | Optional content kind. When omitted, engines infer the kind from the fields present. |\n| `text` | no | `oneOf:string / array<ref:TextRun>` | Text payload. Use a string for plain text or TextRun[] for inline rich text. TextRun items may be plain strings or formatted run objects. |\n| `items` | no | `array<ref:ListItem>` | Generic list payload. Each item is either a plain string, a TextRun[] rich text sequence, or a ListItem object. List nesting uses item.level rather than nested content payloads. |\n| `bullets` | no | `array<ref:BulletItem>` | Text-style bullet payload. Presence of this field infers type 'text'. |\n| `image` | no | `ref:Asset` | Source for an image item. |\n| `video` | no | `ref:Asset` | Source for a video item. |\n| `chart` | no | `ref:Chart` | Chart payload. Presence of this field infers type 'chart'. |\n| `table` | no | `ref:Table` | Table payload. Presence of this field infers type 'table'. |\n| `code` | no | `oneOf:string / ref:Code` | Code payload. A string is shorthand for { \"source\": value }; object form carries optional syntax language and filename metadata. |\n| `metric` | no | `oneOf:string / number / ref:Metric` | Metric payload. A string or number is shorthand for { \"value\": value }; object form carries optional label, description, unit, delta, and trend metadata. Numeric values remain numbers; renderers format them for display. |\n| `quote` | no | `oneOf:string / ref:Quote` | Quote payload. A string is shorthand for { \"text\": value }; object form carries optional attribution and source metadata. |\n| `timeline` | no | `ref:Timeline` | Timeline payload ordered by narrative or chronology. |\n| `blocks` | no | `array<ref:ContentPayload>` | Ordered children of a group. Each child is a leaf or another group. |\n| `composition` | no | `ref:Composition` | Arrangement within this group. Only minFontSize and overflow inherit from the parent; strict overflow cannot be weakened. |\n\n\n### Quote\n\n- Type: `object`\n- Required fields: `text`\n- Purpose: Quote content with optional attribution metadata. Use 'text' for the quoted text, 'attribution' for the credited person or organization, and 'source' for a citation or URL. A string value in a quote field is shorthand for { \"text\": value }.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `text` | yes | `string` | Quoted text. |\n| `attribution` | no | `string` | Person or organization credited for the quote. |\n| `source` | no | `string` | Optional quote source, citation, or URL. |\n\n\n### Code\n\n- Type: `object`\n- Required fields: `source`\n- Purpose: Code content with optional rendering metadata. Use 'source' for the code text, 'language' for syntax highlighting, and 'filename' when the rendered block should show a file label. A string value in a code field is shorthand for { \"source\": value }.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `source` | yes | `string` | Source code text to display. |\n| `language` | no | `string` | Language identifier used for syntax highlighting. |\n| `filename` | no | `string` | Optional file label shown with the code block. |\n\n\n### Metric\n\n- Type: `object`\n- Required fields: `value`\n- Purpose: Metric content with optional display metadata. Use 'value' for the primary value, 'label' for the metric name, 'description' for supporting context, 'unit' for a suffix/currency marker, 'delta' for change, and 'trend' for direction. A string or number value in a metric field is shorthand for { \"value\": value }; numeric values remain numbers and are formatted by renderers.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `value` | yes | `oneOf:string / number` | Primary metric value. |\n| `label` | no | `string` | Metric label. |\n| `description` | no | `string` | Optional supporting context for the metric. |\n| `unit` | no | `string` | Metric unit, suffix, or currency marker. |\n| `delta` | no | `oneOf:string / number` | Metric change value. |\n| `trend` | no | `enum:up \\| down \\| flat` | Metric trend direction. |\n\n\n### Timeline\n\n- Type: `oneOf:array<ref:TimelineEvent> / object`\n- Required fields: none\n- Purpose: Timeline content. An array is shorthand for { \"events\": value }; object form carries optional name and description metadata.\n\n_No named properties._\n\n\n### TimelineEvent\n\n- Type: `object`\n- Required fields: `what`\n- Purpose: A single event inside a timeline content payload.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `when` | no | `string` | Event time, date, or sequence label. Use ISO-like values when possible, but human labels are allowed for quarters, eras, and relative milestones. |\n| `what` | yes | `string` | Short event label. |\n| `description` | no | `string` | Optional event detail. |\n\n\n### ListItem\n\n- Type: `oneOf:string / array<ref:TextRun> / object`\n- Required fields: none\n- Purpose: A flat item inside a list. Strings cover the common case, TextRun[] supports inline rich text without an object wrapper, and object form adds description and nesting depth without creating nested slide content payloads.\n\n_No named properties._\n\n\n### BulletItem\n\n- Type: `oneOf:string / array<ref:TextRun> / object`\n- Required fields: none\n- Purpose: A flat bullet item. Strings cover the common case, TextRun[] supports inline rich text without an object wrapper, and object form adds nesting depth without list-item descriptions.\n\n_No named properties._\n\n\n### TextRun\n\n- Type: `oneOf:string / object`\n- Required fields: none\n- Purpose: A contiguous run of text. Strings cover unformatted spans; object form adds character formatting.\n\n_No named properties._\n\n\n### Chart\n\n- Type: `object`\n- Required fields: `type`, `data`\n- Purpose: Chart content. The chart object keeps chart-specific fields together so slides and regions do not expose loose chart fields.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `string` | Chart type id. Resolves to the id of a chartTypes catalog record; renderers map that record through mappings.openxml and any renderer-specific mapping they understand. |\n| `data` | yes | `oneOf:ref:ChartData / ref:ChartDataSource` | Chart data. Inline data uses a tabular columns/rows shape; renderers convert rows to chart series internally. |\n\n\n### Table\n\n- Type: `object`\n- Required fields: `rows`\n- Purpose: Table content. Columns are optional; rows are the only required field.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `columns` | no | `array<oneOf:string / array<ref:TextRun> / ref:StyledTableCell / null>` | Optional column labels. Labels may be strings, rich runs or styled cell objects. Null is an empty label or a placeholder covered by a preceding column span. |\n| `rows` | yes | `array<array<ref:TableCell>>` | Two-dimensional table row data; each row aligns by index with columns when columns are supplied. |\n\n\n### ChartData\n\n- Type: `object`\n- Required fields: `columns`, `rows`\n- Purpose: Inline tabular data driving a chart. The first column usually supplies category/x-axis labels; subsequent columns are plotted measures unless a chart type or renderer maps them differently.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `columns` | yes | `array<string>` | Ordered column labels for the chart data table. |\n| `rows` | yes | `array<array<ref:ChartDataCell>>` | Tabular chart rows. Each row aligns by index with columns. |\n\n\n### ChartDataSource\n\n- Type: `object`\n- Required fields: `src`\n- Purpose: Chart data sourced from an asset reference, URL, data URI, relative path, or local path such as CSV, TSV, JSON, or XLSX. The source is interpreted as a table; optional columns select or order fields from that table.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `src` | yes | `string` | Data source. Use 'asset:<id>' to reference the top-level assets registry, or provide an HTTPS URL, data URI, relative path, or local filesystem path. |\n| `sheet` | no | `string` | Optional sheet name or table name for spreadsheet-like assets. |\n| `range` | no | `string` | Optional A1-style range or engine-defined range selector for spreadsheet-like assets. |\n| `columns` | no | `array<string>` | Optional ordered columns or fields to read from the source. When omitted, renderers may use the source's own header row or schema. |\n\n\n### ChartDataCell\n\n- Type: `oneOf:string / number / boolean / null`\n- Required fields: none\n- Purpose: A cell in inline chart data.\n\n_No named properties._\n\n\n### TableCell\n\n- Type: `oneOf:ref:TableCellValue / ref:StyledTableCell`\n- Required fields: none\n- Purpose: A scalar, rich-run array, or styled/spanning cell object. Existing scalar and rich forms remain valid.\n\n_No named properties._\n\n\n### TableCellValue\n\n- Type: `oneOf:string / number / boolean / null / array<ref:TextRun>`\n- Required fields: none\n- Purpose: A scalar table value or canonical rich text runs, without cell decoration or geometry.\n\n_No named properties._\n\n\n### StyledTableCell\n\n- Type: `object`\n- Required fields: `value`\n- Purpose: A cell with explicit visual style or merged geometry. Its position remains its array column index; use null placeholders for every covered grid position.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `value` | yes | `ref:TableCellValue` | Editable cell content; styling and spans do not change its scalar type or rich runs. |\n| `style` | no | `ref:TableCellStyle` | |\n| `colSpan` | no | `integer` | Number of grid columns covered, starting at this cell. Covered positions must contain null. Default 1. |\n| `rowSpan` | no | `integer` | Number of grid rows covered, starting at this cell. Covered positions must contain null. Header cells cannot span into body rows. Default 1. |\n\n\n### TableCellStyle\n\n- Type: `object`\n- Required fields: none\n- Purpose: Cell appearance. Sizes use reference pixels at a 720-pixel canvas short edge and scale with the slide.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `fill` | no | `string` | Explicit RGB or RGBA color. Eight-digit colors include alpha; #00000000 is transparent. |\n| `color` | no | `string` | Default text color, overridden by individual rich run colors. |\n| `align` | no | `enum:left \\| center \\| right` | Horizontal text alignment inside the cell. |\n| `verticalAlign` | no | `enum:top \\| middle \\| bottom` | Vertical alignment inside the padded cell box. |\n| `padding` | no | `ref:TableCellPadding` | |\n| `borders` | no | `object` | Independent cell edges. Omitted edges retain the table theme border; width 0 removes an edge. |\n\n\n### TableCellPadding\n\n- Type: `object`\n- Required fields: none\n- Purpose: Text insets in reference pixels. Defaults: top 8, right 10, bottom 4, left 10.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `top` | no | `number` | |\n| `right` | no | `number` | |\n| `bottom` | no | `number` | |\n| `left` | no | `number` | |\n\n\n### TableCellBorder\n\n- Type: `object`\n- Required fields: `color`, `width`\n- Purpose: One explicit cell border.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `color` | yes | `string` | Explicit RGB or RGBA color. Eight-digit colors include alpha; #00000000 is transparent. |\n| `width` | yes | `number` | Border width in reference pixels; 0 removes this edge. |\n| `dash` | no | `enum:solid \\| dash \\| dot` | Default solid. |\n\n\n### Catalogs\n\n- Type: `object`\n- Required fields: none\n- Purpose: Catalog overrides for the in-document references. Every property is optional. The default catalog for a kind lives at https://www.pptx.gallery/<kind> (e.g. https://www.pptx.gallery/narratives, https://www.pptx.gallery/themes). For each kind, declaring a 'source' replaces the default registry and/or 'records' adds inline records that take precedence over anything fetched from a source. Resolution order for any reference (e.g. narrative, design.theme): inline catalogs.<kind>.records[] catalogs....\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `narratives` | no | `ref:CatalogEntry` | Catalog of narrative templates. Records validate against https://openpresentation.org/schema/opf-narrative/v1. Default source: https://www.pptx.gallery/narratives. |\n| `themes` | no | `ref:CatalogEntry` | Catalog of themes. Records validate against https://openpresentation.org/schema/opf-theme/v1. Default source: https://www.pptx.gallery/themes. |\n| `colorSchemes` | no | `ref:CatalogEntry` | Catalog of color schemes. Records validate against https://openpresentation.org/schema/opf-color-scheme/v1. Default source: https://www.pptx.gallery/color-schemes. |\n| `fontSchemes` | no | `ref:CatalogEntry` | Catalog of font schemes. Records validate against https://openpresentation.org/schema/opf-font-scheme/v1. Default source: https://www.pptx.gallery/font-schemes. |\n| `languages` | no | `ref:CatalogEntry` | Catalog of languages. Records validate against https://openpresentation.org/schema/opf-language/v1. Default source: https://www.pptx.gallery/languages. |\n| `layouts` | no | `ref:CatalogEntry` | Catalog of slide layouts. Records validate against https://openpresentation.org/schema/opf-layout/v1. Default source: https://www.pptx.gallery/layouts. |\n| `chartTypes` | no | `ref:CatalogEntry` | Catalog of chart types. Records validate against https://openpresentation.org/schema/opf-chart-type/v1. Default source: https://www.pptx.gallery/chart-types. |\n| `tones` | no | `ref:CatalogEntry` | Catalog of presentation tones. Records validate against https://openpresentation.org/schema/opf-tone/v1. Default source: https://www.pptx.gallery/tones. Referenced from tone. |\n| `purposes` | no | `ref:CatalogEntry` | Catalog of presentation purposes. Records validate against https://openpresentation.org/schema/opf-purpose/v1. Default source: https://www.pptx.gallery/purposes. Referenced from purpose. |\n| `audiences` | no | `ref:CatalogEntry` | Catalog of presentation audiences. Records validate against https://openpresentation.org/schema/opf-audience/v1. Default source: https://www.pptx.gallery/audiences. Referenced from audience. |\n| `socialPlatforms` | no | `ref:CatalogEntry` | Catalog of social-media platforms. Records validate against https://openpresentation.org/schema/opf-social-platform/v1. Default source: https://www.pptx.gallery/social-platforms. Referenced via the property keys of an... |\n\n\n### CatalogEntry\n\n- Type: `object`\n- Required fields: none\n- Purpose: A catalog override for one record kind. 'source' replaces the default registry; 'records' adds inline records that take precedence over anything fetched from a source. Either or both may be provided; both omitted means the kind uses its default catalog.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `source` | no | `oneOf:ref:CatalogSource / array<ref:CatalogSource>` | Single source or an ordered search path of sources. When omitted, the engine falls back to https://www.pptx.gallery/<kind>. |\n| `records` | no | `array<object>` | Inline catalog records embedded in this OPF document. Each record validates against the kind's companion schema (e.g. https://openpresentation.org/schema/opf-narrative/v1 for narratives). Inline records win over anyth... |\n\n\n### CatalogSource\n\n- Type: `string`\n- Required fields: none\n- Purpose: Catalog source location. Accepts: - A bare URL pointing at a catalog directory (e.g. 'https://acme.com/decks/narratives'); record ids resolve to '<base>/<id>.json'. - A URL pointing at an index file (e.g. 'https://acme.com/decks/narratives/index.json'); records are resolved relative to the index file's directory and the index entries describe what's available. - A package reference of the form 'pkg:<package>[/<subpath>]'; resolved through a locally-installed package on the engine's package path.\n\n_No named properties._\n\n\n### Composition\n\n- Type: `object`\n- Required fields: none\n- Purpose: Portable dynamic composition. Slide fields override the resolved layout. Nested groups arrange their children independently, inheriting only minFontSize and overflow. Explicit promoted regions retain their positions.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `mode` | no | `enum:auto \\| grid \\| row \\| column` | auto chooses a grid from available space and content; grid uses columns; row and column use one horizontal or vertical track. |\n| `columns` | no | `integer` | Column count for grid. In auto mode this caps the number of columns. |\n| `gap` | no | `number` | Space between cells as a fraction of the container short edge (canvas at slide root). Default 0.03333333333333333. |\n| `padding` | no | `number` | Inset as a fraction of the container short edge. Default 0.08 on a slide, 0 inside a group. |\n| `weights` | no | `array<number>` | Relative track sizes: columns for row/grid/auto, rows for column. Omitted tracks have weight 1; extra weights are ignored. |\n| `minFontSize` | no | `number` | Minimum readable text size in reference pixels at a 720-pixel canvas short edge. Default 16. Overflow is diagnosed when text cannot fit at this size. |\n| `overflow` | no | `enum:warn \\| error` | warn returns diagnostics for content that does not fit; error rejects layout. Content is never silently removed. Default warn. |\n"
229
+ "markdown": "# OPF Presentation Schema Reference\n\nThis reference documents the author-facing shape of a complete `*.opf.json` presentation document. It summarizes the canonical schema in `spec/schemas/opf.schema.json`; the schema remains the source of truth for validators.\n\n## Document Contract\n\n- Schema id: `https://openpresentation.org/schema/opf/v1`\n- Required top-level fields: `slides`\n- Additional top-level fields: not allowed\n\n## Top-Level Fields\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `$schema` | no | `const:\"https://openpresentation.org/schema/opf/v1\"` | Optional OPF schema version. When omitted, validators and engines should assume the latest supported OPF schema. |\n| `name` | no | `string` | Display name of the presentation for GUI/TUI lists, library/search indexing, OS-level metadata, and default export filenames. This is deck identity, not slide content. Use slides[].title and slides[].subtitle for text... |\n| `description` | no | `string` | Free-form prose describing what this presentation is about. Used by agents and humans as a deck-level summary; complements purpose (the goal) and narrative (the structured storyline). Round-trips to OOXML 'docProps/co... |\n| `filename` | no | `string` | Optional base filename for exports (without extension). Engine strips a trailing .pptx, .pdf, .png, or .svg (case-insensitive) and appends the target format's extension. When omitted, the engine slugifies name when pr... |\n| `organization` | no | `oneOf:ref:Organization / array<ref:Organization>` | Organization associated with the presentation, usually the presenting company. Array form supports hosts, partners, clients, and sponsors. The primary organization (declared via Organization.role or, if no role is set... |\n| `speaker` | no | `oneOf:ref:Speaker / array<ref:Speaker>` | Person presenting the deck. Array form supports panels and multi-speaker decks. Used for cover slides, bio slides, footers, and panel attribution. |\n| `author` | no | `oneOf:string / array<string>` | Optional credit for the person who authored or contributed to the deck, distinct from speaker. Array form supports multiple contributors. Round-trips to OOXML 'docProps/core.xml' as '<dc:creator>' (semicolon-joined wh... |\n| `audience` | no | `oneOf:string / array<oneOf:string / ref:Audience>` | Intended audiences for the presentation. Accepts either: - A single string shorthand: free-form description ('Series B investors'), an audiences catalog id ('executives'), an HTTPS URL, or a 'pkg:' reference. - An arr... |\n| `purpose` | no | `oneOf:string / ref:Purpose` | Primary goal of the presentation. Accepts either: - A string shorthand: free-form goal ('Raise a Series B round of $30M'), a purposes catalog id ('decide', 'align'), an HTTPS URL, or a 'pkg:' reference. - An inline Pu... |\n| `language` | no | `oneOf:string / ref:Language` | Language for the presentation content. Accepts either: - A string shorthand: a BCP-47 language tag ('en-US', 'en-GB', 'ja-JP', 'fr'), a languages catalog id ('english', 'japanese'), an HTTPS URL, or a 'pkg:' reference... |\n| `tone` | no | `oneOf:string / ref:Tone` | Desired tone for the presentation. Accepts either: - A string shorthand: a tones catalog id ('formal'), an HTTPS URL, or a 'pkg:' reference. - An inline Tone object for custom tone metadata or catalog-backed overrides... |\n| `takeaway` | no | `oneOf:string / array<string>` | Audience-facing takeaway the presentation should leave behind. Array form supports multiple takeaways. Deck-level intent used by AI to seed and pressure-test slide content. |\n| `duration` | no | `integer` | Target presentation duration, as an integer number of minutes. Used by AI to set pace and depth, and to compare against the resolved narrative's durationRange. |\n| `tags` | no | `array<string>` | Free-form labels used for categorization, search, and filtering. Lowercase kebab-case is recommended for consistency across a deck library. |\n| `design` | no | `ref:Design` | Optional design system covering theme, color scheme, font scheme, dimensions, background, logo, watermark, header, and footer applied to the deck. When omitted, engines use their default design configuration. |\n| `variables` | no | `ref:Variables` | Optional named color variables for values the deck uses in more than one place or wants to name for intent (e.g. a risk red, a brand highlight). Content color fields reference entries as 'var:<id>' strings. Variables... |\n| `narrative` | no | `oneOf:string / ref:Narrative` | Structured storyline describing the deck's arc and beats. Resolves to the 'id' of a 'narratives' catalog record. Accepts two forms: - String shorthand for the common case: 'narrative = \"classic-story\"'. Accepts a bare... |\n| `slides` | yes | `array<ref:Slide>` | Ordered array of slides that make up the presentation. |\n| `assets` | no | `ref:Assets` | Optional reusable asset registry for images, data files, videos, documents, fonts, and other resources referenced elsewhere in the deck via 'asset:<id>' strings. |\n| `catalogs` | no | `ref:Catalogs` | Optional per-kind catalog overrides. Each kind may declare a non-default 'source' and/or inline 'records' that override or supplement the default catalog at https://www.pptx.gallery/<kind>. References elsewhere in the... |\n| `extensions` | no | `object` | Custom data passthrough for agent workflows; ignored by the engine but preserved across read/write round-trips. |\n\n## Object And Type Reference\n\n### Assets\n\n- Type: `object`\n- Required fields: none\n- Purpose: Reusable asset registry for resources used by slides, charts, metadata, and design. Keys are stable asset ids referenced elsewhere as 'asset:<id>'. Each asset can be a source string or an object with src plus optional metadata.\n\n_No named properties._\n\n\n### Asset\n\n- Type: `oneOf:string / object`\n- Required fields: none\n- Purpose: Reusable or inline resource. A string is shorthand for { \"src\": value }. Source strings accept 'asset:<id>' references, HTTPS URLs, data URIs, relative paths resolved against the OPF file location, or local filesystem paths. Use object form when metadata such as alt text, title, mediaType, or format matters.\n\n_No named properties._\n\n\n### Audience\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline audience metadata for the presentation. Use 'id' to reference an audiences catalog record and override selected fields, or use 'name' for a custom inline audience.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional audiences catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable audience name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the audience. |\n| `description` | no | `string` | Longer prose describing the audience and how to address them. |\n| `seniority` | no | `enum:ic \\| manager \\| director \\| vp \\| c-suite \\| mixed` | Typical seniority level of the audience. |\n| `technicalFluency` | no | `enum:low \\| medium \\| high \\| mixed` | Typical technical fluency of the audience. |\n| `decisionPower` | no | `enum:informational \\| advisory \\| decision-maker` | Whether the audience is expected to be informed, advise, or decide. |\n| `attentionBudgetMinutes` | no | `number` | Realistic upper bound on focused attention for a single presentation, in minutes. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this audience. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this audience. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Purpose\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline purpose metadata for the presentation. Use 'id' to reference a purposes catalog record and override selected fields, or use 'name' for a custom inline purpose.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional purposes catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable purpose name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the purpose. |\n| `description` | no | `string` | Longer prose describing when to use this purpose and how it should shape a deck. |\n| `outcome` | no | `string` | Desired audience outcome after the presentation. |\n| `successCriteria` | no | `array<string>` | Observable signals that the deck accomplished this purpose. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids that work well for this purpose. |\n| `recommendedTones` | no | `array<string>` | Soft cross-link: tone-catalog ids that work well for this purpose. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Language\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline language metadata for the presentation. Use 'id' to reference a languages catalog record and override selected fields, or use 'bcp47' for a custom language tag without a catalog record.\n- Conditional requirement: `id` or `bcp47`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional languages catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable language name. |\n| `bcp47` | no | `string` | BCP-47 language tag used for locale-aware rendering, proofing, and accessibility metadata. Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region form. |\n| `ooxmlLang` | no | `string` | Curated culture tag for OOXML text-run language attributes (a:rPr/@lang, a:endParaRPr/@lang), in the language-[Script-]REGION form Office recognizes (e.g. 'ja-JP', 'ar-SA', 'ms-MY', 'nb-NO', 'fil-PH', 'zh-CN'). Engine... |\n| `code` | no | `string` | ISO 639-3 or 639-2 language code carried for engines that prefer ISO codes. |\n| `direction` | no | `enum:ltr \\| rtl` | Base text direction for the language. When omitted, engines derive it from the script: Arabic (Arab), Hebrew (Hebr), Syriac (Syrc), Thaana (Thaa), N'Ko (Nkoo), Adlam (Adlm), Samaritan (Samr), Mandaic (Mand) and Hanifi... |\n| `script` | no | `string` | ISO 15924 script code of the language's writing system. The script selects the OOXML font slot the language's text uses: East Asian scripts (Hans, Hant, Hani, Jpan, Kore, Hang, Hira, Kana, Bopo, Yiii) use the eastAsia... |\n| `fontScheme` | no | `string` | Default font-scheme id for this language when targeting PowerPoint output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. Its major/minor families fill the language'... |\n| `googleFontScheme` | no | `string` | Default font-scheme id for this language when targeting Google Slides output. Resolves against catalogs.fontSchemes the same way design.fontScheme or design.fontScheme.id does. Used in place of 'fontScheme' when resol... |\n| `summary` | no | `string` | One-sentence note about coverage or font defaults. |\n| `description` | no | `string` | Longer prose describing the language record and any font-pairing rationale. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Tone\n\n- Type: `anyOf:schema / schema`\n- Required fields: none\n- Purpose: Inline tone metadata for the presentation. Use 'id' to reference a tones catalog record and override selected fields, or use 'name' for a custom inline tone.\n- Conditional requirement: `id` or `name`\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional tones catalog id to resolve before applying inline overrides. |\n| `name` | no | `string` | Human-readable tone name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the tone. |\n| `description` | no | `string` | Longer prose describing the tone and the kinds of decks it suits. |\n| `voiceCues` | no | `array<string>` | Short directives that shape AI generation toward this tone. |\n| `avoid` | no | `array<string>` | Anti-patterns that AI generation should not produce when this tone is active. |\n| `samplePhrases` | no | `array<string>` | Short example phrases that exemplify this tone. |\n| `recommendedNarratives` | no | `array<string>` | Soft cross-link: narrative-catalog ids this tone pairs well with. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### Organization\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: An organization associated with the presentation typically the presenting company, but also hosts, partners, clients, or sponsors. Surfaced on cover slides, footers, and brand bars; the primary organization's logo is the default deck logo unless overridden by design.logo.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier for the organization, used to reference it from Speaker.organizationId. Must be unique within the deck. |\n| `name` | yes | `string` | Display name shown on slides. |\n| `legalName` | no | `string` | Optional legal entity name when it differs from the display name. |\n| `logo` | no | `ref:Asset` | Source for the organization's logo image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are SVG (preferred for vector log... |\n| `domain` | no | `string` | Bare internet domain for the organization. Used for footers, contact slides, and engine-driven asset lookups (e.g., favicon-based brand defaults). |\n| `email` | no | `string` | General contact email for the organization. Used on contact slides and footer attribution. |\n| `phone` | no | `string` | Main contact phone number for the organization. E.164 format is recommended. |\n| `tagline` | no | `string` | Short tagline rendered alongside the organization name on cover slides. |\n| `role` | no | `enum:primary \\| partner \\| client \\| sponsor \\| host` | Role of the organization relative to the presentation. When omitted, the single organization or first organization in array form is treated as primary. |\n| `socials` | no | `ref:Socials` | Optional social media handles or URLs for the organization. The primary organization's socials render in header/footer zones that set socials: true; otherwise they are authoring metadata. |\n\n\n### Speaker\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A person presenting the deck. Used for cover slides, bio/intro slides, footer attribution, and panel formats with multiple presenters.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable identifier for the speaker, used for cross-references within the deck. Must be unique within the deck. |\n| `name` | yes | `string` | Display name. |\n| `title` | no | `string` | Role or title. Often paired with the speaker's organization on cover slides. |\n| `photo` | no | `ref:Asset` | Source for the speaker's headshot image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are JPG or PNG; SVG is not appropr... |\n| `email` | no | `string` | Contact email, used on contact slides or footer attribution when appropriate. |\n| `phone` | no | `string` | Contact phone number for the speaker. E.164 format is recommended. |\n| `bio` | no | `string` | Short biographical paragraph for bio or 'about the speaker' slides. |\n| `organizationId` | no | `string` | Reference to an Organization.id in organization. Lets a speaker be attributed to their org in panel or multi-org decks without repeating organization details. |\n| `socials` | no | `ref:Socials` | Optional social media handles or URLs for the speaker. Authoring metadata: no header/footer field renders speaker socials yet. |\n\n\n### Socials\n\n- Type: `object`\n- Required fields: none\n- Purpose: Social media handles or URLs, keyed by platform id from the 'socialPlatforms' catalog. Each value is a string either a full URL or a platform handle (e.g., '@acme'). The catalog record for each platform carries the URL pattern, handle prefix, brand color, and themed icons used by renderers. Keys resolve to the 'id' of a 'socialPlatforms' catalog record. Resolution order: inline catalogs.socialPlatforms.records[] catalogs.socialPlatforms.source default catalog at https://www.pptx.gallery/socia...\n\n_No named properties._\n\n\n### Narrative\n\n- Type: `object`\n- Required fields: none\n- Purpose: Structured storyline used by AI to shape generated content. Mirrors the OPF Narrative Template record at https://openpresentation.org/schema/opf-narrative/v1 (sans '$schema'), so a library record and an inline narrative are interchangeable. Narrative declares the deck's intended story arc; slides may opt into beats via Slide.beat. The narrative does not constrain slide structure validators warn on drift (orphan slides, unused beats) but never error. Slides are the source of truth; narrative i...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Stable slug identifying this narrative. When it matches a record in the resolved 'narratives' catalog, the catalog record's beats and metadata seed this narrative; inline fields override per-key. When it doesn't match... |\n| `name` | no | `string` | Human-readable narrative name. |\n| `summary` | no | `string` | One-sentence description of when and why to use this narrative. |\n| `description` | no | `string` | Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction. |\n| `audienceFit` | no | `array<string>` | Audiences this narrative works well for. Free-form strings or 'audiences' catalog ids. |\n| `durationRange` | no | `object` | Typical talk-length window this narrative suits. Compared by validators against duration. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n| `preview` | no | `object` | Visual previews of the narrative, used by picker UIs and inline rendering. All sub-fields are optional. |\n| `beats` | no | `array<ref:NarrativeBeat>` | Ordered list of beats that make up the narrative arc. When 'id' matches a catalog record, beats here override or extend matching catalog beats by their own 'id'. Beat IDs must be unique within the narrative. |\n\n\n### NarrativeBeat\n\n- Type: `object`\n- Required fields: `id`, `name`\n- Purpose: A single narrative beat a labeled segment of the story arc with a specific dramatic purpose (e.g. 'hook', 'problem', 'evidence', 'ask'). Slides reference beats via Slide.beat. Beats may also carry slide-blueprint hints (slideType, layoutHint, thoughtCues, instructions) that guide the assigned slide. Mirrors the Beat definition in narrative.schema.json (https://openpresentation.org/schema/opf-narrative/v1) so library entries and inline OPF beats are interchangeable.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | yes | `string` | Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case. |\n| `name` | yes | `string` | Human-readable beat name. |\n| `description` | no | `string` | Curator-written prose that explains what this beat should accomplish. |\n| `instructions` | no | `string` | Short author-facing instruction for the beat typically one phrase. Complements 'description' with a concise directive. |\n| `slideCount` | no | `integer` | Optional explicit slide count for this beat. Defaults to 1 when omitted; values >1 are reserved for beats that intentionally span multiple slides. Prefer decomposing a heavy beat into multiple beats over setting a hig... |\n| `slideType` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Default content kind for the beat's slide. Mirrors ContentPayload.type and helps engines choose a sensible layout when only the beat is specified. |\n| `layoutHint` | no | `string` | Suggested layout id for the beat's opening slide. Resolves the same way as Slide.layout against catalogs.layouts and the default catalog at https://www.pptx.gallery/layouts. |\n| `thoughtCues` | no | `array<string>` | Optional speaker or thinking cues attached to the beat. Surfaced in presenter notes. |\n\n\n### Design\n\n- Type: `object`\n- Required fields: none\n- Purpose: Visual design system applied to the presentation; individual slides may override fields via Slide.design.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `theme` | no | `oneOf:string / ref:Theme` | Theme for the deck. Accepts two forms: - String shorthand: 'design.theme = \"minimal\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'themes' catalog record. - Object form: a Theme with an optional... |\n| `colorScheme` | no | `oneOf:string / ref:ColorScheme` | Color scheme for the presentation. Accepts two forms: - String shorthand: 'design.colorScheme = \"cool-horizon\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'colorSchemes' catalog record. - Objec... |\n| `fontScheme` | no | `oneOf:string / ref:FontScheme` | Font scheme for heading, body, accent, and code text. Accepts two forms: - String shorthand: 'design.fontScheme = \"aptos\"'. Bare id, HTTPS URL, or 'pkg:' reference resolved as the 'id' of a 'fontSchemes' catalog recor... |\n| `dimensions` | no | `oneOf:ref:DimensionPreset / ref:Dimensions` | Slide dimensions and aspect ratio. String shorthand such as 'widescreen' is equivalent to { preset: 'widescreen' }. |\n| `background` | no | `oneOf:ref:BackgroundShortcut / ref:Background` | Default slide background applied across the deck unless overridden on a slide. String shorthand accepts theme slots ('light1', 'light2', 'dark1', 'dark2') or hex colors; object forms support theme, solid, gradient, im... |\n| `logo` | no | `oneOf:ref:Asset / ref:LogoSet` | Deck logo assets used by layouts, covers, section dividers, headers, and footers. A string is the default logo source; object form provides light/dark, stacked, icon, and wordmark variants. When omitted, the renderer... |\n| `watermark` | no | `oneOf:const:false / ref:Asset / ref:Watermark` | Optional decorative watermark applied across slides. Use false to suppress an inherited watermark in slide-level design; a string is equivalent to { src: value }. |\n| `header` | no | `oneOf:const:false / ref:HeaderFooter` | Repeated header furniture rendered outside the main slide content. Use false to suppress an inherited header. |\n| `footer` | no | `oneOf:const:false / ref:HeaderFooter` | Repeated footer furniture rendered outside the main slide content. Use false to suppress an inherited footer. |\n| `titleAlignment` | no | `enum:left \\| center \\| right` | Default horizontal alignment for title placeholders in resolved layouts. |\n| `contentAlignment` | no | `enum:left \\| center \\| right` | Default horizontal alignment for body/content regions in resolved layouts. |\n| `contentBox` | no | `boolean` | Whether body/content regions are rendered inside a visible card or surface. |\n| `slideImage` | no | `oneOf:ref:Asset / object` | Optional slide-level image, separate from content images. It applies to a slide that sets its own design.slideImage, and to slides whose layout declares slideImage: true or whose root image is the same source as a dec... |\n| `contentDirection` | no | `enum:horizontal \\| vertical` | Axis along which parallel body/content regions are arranged. |\n| `chartPrimary` | no | `enum:none \\| top \\| bottom \\| left \\| right` | For chart layouts, where the primary chart sits relative to supporting content. 'none' means chart regions have equal weight. |\n| `imageFill` | no | `enum:crop \\| fit` | How picture placeholders fill their allocated region. |\n| `listBullet` | no | `enum:character \\| image` | Default bullet rendering style for list layouts. |\n\n\n### Theme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Theme bundle used by the design system. In design.theme, 'id' resolves a themes catalog record as the base; any sibling fields override the resolved theme. The string shorthand on design.theme is equivalent to setting only 'id'.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Theme reference. Resolves to the 'id' of a 'themes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'minimal'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Field overrides on the surro... |\n| `name` | no | `string` | Human-readable theme name shown in pickers. |\n| `summary` | no | `string` | One-sentence positioning of the theme - when to reach for it. |\n| `description` | no | `string` | Longer prose describing what the theme looks and feels like and the kinds of decks it suits. |\n| `colorScheme` | no | `oneOf:string / ref:ColorScheme` | Default color scheme for this theme. A string resolves against catalogs.colorSchemes; an object may provide an 'id' base reference plus overrides. |\n| `fontScheme` | no | `oneOf:string / ref:FontScheme` | Default font scheme for this theme. A string resolves against catalogs.fontSchemes; an object may provide an 'id' base reference plus overrides. |\n| `background` | no | `oneOf:ref:BackgroundShortcut / ref:Background` | Default background for this theme. String shorthand accepts theme slots ('light1', 'light2', 'dark1', 'dark2') or hex colors. |\n| `dimensions` | no | `oneOf:ref:DimensionPreset / ref:Dimensions` | Default slide size for this theme. A string preset is equivalent to { preset: value }. |\n| `tags` | no | `array<string>` | Free-form labels for filtering and search. |\n\n\n### ColorScheme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Color palette used by the design system. The slot fields (accent1-accent6, dark1, dark2, light1, light2, hyperlink, followedHyperlink) mirror color-scheme.schema.json (https://openpresentation.org/schema/opf-color-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields. Two parallel models are supported and may be mixed: - OOXML slots - the 12-slot PowerPoint theme model that round-trips directly to OOXML. Use these for full control over the palette as Power...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Color scheme reference. Resolves to the 'id' of a 'colorSchemes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'cool-horizon'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Slot and r... |\n| `accent1` | no | `string` | Accent 1 color (hex). Mirrors the OOXML accent1 slot. |\n| `accent2` | no | `string` | Accent 2 color (hex). Mirrors the OOXML accent2 slot. |\n| `accent3` | no | `string` | Accent 3 color (hex). Mirrors the OOXML accent3 slot. |\n| `accent4` | no | `string` | Accent 4 color (hex). Mirrors the OOXML accent4 slot. |\n| `accent5` | no | `string` | Accent 5 color (hex). Mirrors the OOXML accent5 slot. |\n| `accent6` | no | `string` | Accent 6 color (hex). Mirrors the OOXML accent6 slot. |\n| `dark1` | no | `string` | Dark 1 color (hex). Typically the deepest neutral; OOXML dark1. |\n| `dark2` | no | `string` | Dark 2 color (hex). Secondary dark; OOXML dark2. |\n| `light1` | no | `string` | Light 1 color (hex). Typically the slide canvas; OOXML lt1. |\n| `light2` | no | `string` | Light 2 color (hex). Secondary light surface; OOXML lt2. |\n| `hyperlink` | no | `string` | Hyperlink color (hex). OOXML hlink. |\n| `followedHyperlink` | no | `string` | Followed-hyperlink color (hex). OOXML folHlink. |\n| `primary` | no | `string` | Abstract role: primary brand color (hex). The engine maps this onto an OOXML accent slot when serializing. |\n| `secondary` | no | `string` | Abstract role: secondary brand color (hex). |\n| `accent` | no | `string` | Abstract role: accent color used for highlights and emphasis (hex). |\n| `background` | no | `string` | Abstract role: default slide background color (hex). The engine maps this to one of light1 / light2 / dark1 / dark2 when serializing. |\n| `surface` | no | `string` | Abstract role: color for elevated surfaces such as cards and panels (hex). |\n| `text` | no | `string` | Abstract role: primary body text color (hex). |\n| `textSecondary` | no | `string` | Abstract role: secondary or muted text color used for captions and supporting copy (hex). |\n| `custom` | no | `object` | Map of custom named colors for advanced or theme-specific use. |\n\n\n### FontScheme\n\n- Type: `object`\n- Required fields: none\n- Purpose: Typography selections used by the design system. The pair fields (major, minor) and refinement fields (type, app, languageFamily) mirror font-scheme.schema.json (https://openpresentation.org/schema/opf-font-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields. Two parallel models are supported and may be mixed: - OOXML pairs (major, minor) - heading and body family names that round-trip directly to PowerPoint majorFont/minorFont entries. - Abstract roles...\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Font scheme reference. Resolves to the 'id' of a 'fontSchemes' catalog record. Accepts a bare id (lowercase kebab-case, e.g. 'aptos'), an HTTPS URL pointing at a record file, or a 'pkg:' reference. Field overrides on... |\n| `major` | no | `string` | Heading (major) font family mirrors the OOXML majorFont entry. Pairs with 'minor'. |\n| `minor` | no | `string` | Body (minor) font family mirrors the OOXML minorFont entry. Pairs with 'major'. |\n| `eastAsian` | no | `object` | East Asian script fonts. Maps to the OOXML a:ea element of majorFont (major) and minorFont (minor), and to run-level a:ea. When set, they fill the eastAsian slot for every language; when omitted, the slot comes from t... |\n| `complexScript` | no | `object` | Complex-script fonts (for example Arabic, Hebrew, Indic and Thai). Maps to the OOXML a:cs element of majorFont (major) and minorFont (minor), and to run-level a:cs. When set, they fill the complexScript slot for every... |\n| `type` | no | `enum:sans-serif \\| serif \\| monospace` | High-level typographic class of the scheme. |\n| `app` | no | `enum:PowerPoint \\| Google Slides` | Target application this font pairing is intended for. |\n| `languageFamily` | no | `enum:latin \\| ea \\| cs` | OOXML font-language family this scheme is intended for: 'latin' for Latin-script content, 'ea' for East Asian scripts, 'cs' for Complex Scripts. As the design font scheme, an 'ea' or 'cs' scheme also fills that script... |\n| `heading` | no | `ref:Font` | Abstract role: font used for slide titles and headings. Maps onto the OOXML major slot when serializing. |\n| `body` | no | `ref:Font` | Abstract role: font used for body copy. Maps onto the OOXML minor slot when serializing. |\n| `accent` | no | `ref:Font` | Abstract role: font used for accent text such as quotes or callouts. No direct OOXML slot. |\n| `code` | no | `ref:Font` | Abstract role: monospaced font used for code blocks and inline code. No direct OOXML slot. Resolution: this override, then the resolved catalog record's 'code' (for example Consolas for the consolas scheme), then the... |\n\n\n### Font\n\n- Type: `object`\n- Required fields: `family`\n- Purpose: Specification for a single font role.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `family` | yes | `string` | Font family name. |\n| `weight` | no | `number` | Numeric font weight (e.g., 400 for regular, 700 for bold). |\n| `style` | no | `enum:normal \\| italic` | Font style. |\n| `letterSpacing` | no | `number` | Letter spacing (tracking) in ems. |\n\n\n### DimensionPreset\n\n- Type: `enum:16:9 | 4:3 | 16:10 | letter | a4 | widescreen | standard`\n- Required fields: none\n- Purpose: Named dimension preset; chooses both aspect ratio and physical size. 'widescreen' is an alias for 16:9 in PowerPoint widescreen size; 'standard' is an alias for 4:3 in PowerPoint standard size.\n\n_No named properties._\n\n\n### Dimensions\n\n- Type: `object`\n- Required fields: none\n- Purpose: Slide dimensions; either pick a preset or specify custom inches.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `preset` | no | `ref:DimensionPreset` | |\n| `widthInches` | no | `number` | Custom slide width in inches; overrides the preset width when provided. |\n| `heightInches` | no | `number` | Custom slide height in inches; overrides the preset height when provided. |\n\n\n### ThemeBackgroundSlot\n\n- Type: `enum:light1 | light2 | dark1 | dark2`\n- Required fields: none\n- Purpose: PowerPoint theme-controlled slide background slot from the active color scheme. These are slots, not assumptions about actual colors: light1 is usually white and dark1 is usually black by convention, but the color scheme controls the real values.\n\n_No named properties._\n\n\n### HexColor\n\n- Type: `string`\n- Required fields: none\n- Purpose: Hex color shorthand accepted by selected string fields.\n\n_No named properties._\n\n\n### ColorRef\n\n- Type: `anyOf:ref:HexColor / enum:accent1 | accent2 | accent3 | accent4 | accent5 | accent6 | dark1 | dark2 | light1 | light2 | hyperlink | followedHyperlink | primary | secondary | accent | background | surface | text | textSecondary / string`\n- Required fields: none\n- Purpose: A color value or reference, enforced on styled table cell fill and text colors and on cell border colors. Three forms: - Literal hex: '#RGB', '#RRGGBB', or '#RRGGBBAA'. - Color-scheme name, resolved through the effective color scheme after design resolution: an OOXML slot ('accent1'-'accent6', 'dark1', 'dark2', 'light1', 'light2', 'hyperlink', 'followedHyperlink') or an abstract role ('primary', 'secondary', 'accent', 'background', 'surface', 'text', 'textSecondary'). Roles resolve through th...\n\n_No named properties._\n\n\n### Variables\n\n- Type: `object`\n- Required fields: none\n- Purpose: Named color variables, keyed by stable kebab-case id. Content color fields reference entries as 'var:<id>' strings. Each value is a hex string shorthand or a Variable object.\n\n_No named properties._\n\n\n### Variable\n\n- Type: `oneOf:ref:HexColor / object`\n- Required fields: none\n- Purpose: A single named variable. A hex string is shorthand for { \"type\": \"color\", \"value\": value }.\n\n_No named properties._\n\n\n### BackgroundShortcut\n\n- Type: `oneOf:ref:ThemeBackgroundSlot / ref:HexColor`\n- Required fields: none\n- Purpose: String shorthand for a background. Theme slots ('light1', 'light2', 'dark1', 'dark2') are equivalent to { type: 'theme', slot: value }; hex colors are equivalent to { type: 'solid', color: value }.\n\n_No named properties._\n\n\n### Background\n\n- Type: `oneOf:ref:ThemeBackground / ref:SolidBackground / ref:GradientBackground / ref:ImageBackground / ref:PatternBackground`\n- Required fields: none\n- Purpose: Background fill applied to slides. Theme backgrounds preserve PowerPoint's color-scheme background choice; other variants represent fixed background fills.\n\n_No named properties._\n\n\n### ThemeBackground\n\n- Type: `object`\n- Required fields: `type`, `slot`\n- Purpose: Theme-controlled PowerPoint slide background. The slot is resolved through the active color scheme and remains theme-aware.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"theme\"` | Theme-controlled background fill. |\n| `slot` | yes | `ref:ThemeBackgroundSlot` | |\n\n\n### SolidBackground\n\n- Type: `object`\n- Required fields: `type`, `color`\n- Purpose: Fixed solid slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"solid\"` | Fixed solid background fill. |\n| `color` | yes | `string` | Fixed solid fill color, usually a hex string. Use { type: 'theme', slot: ... } for PowerPoint's four theme-controlled background choices. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### GradientBackground\n\n- Type: `object`\n- Required fields: `type`, `gradient`\n- Purpose: Fixed gradient slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"gradient\"` | Fixed gradient background fill. |\n| `gradient` | yes | `object` | Gradient fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### ImageBackground\n\n- Type: `object`\n- Required fields: `type`, `image`\n- Purpose: Fixed image slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"image\"` | Fixed image background fill. |\n| `image` | yes | `object` | Image fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### PatternBackground\n\n- Type: `object`\n- Required fields: `type`, `pattern`\n- Purpose: Fixed pattern slide background fill.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `const:\"pattern\"` | Fixed pattern background fill. |\n| `pattern` | yes | `object` | Pattern fill definition. |\n| `opacity` | no | `number` | Background opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### LogoSet\n\n- Type: `object`\n- Required fields: none\n- Purpose: Deck logo variants surfaced by layouts, covers, section dividers, headers, and footers. Organization identity lives in organization; this object only controls visual rendering assets. Renderer convention: on dark backgrounds prefer the 'light' variant, on light backgrounds prefer the 'dark' variant, and in square/vertical slots prefer the stacked family when present.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `default` | no | `ref:Asset` | Default full-lockup logo. Used as fallback when no more specific variant is set. |\n| `light` | no | `ref:Asset` | Light-colored full-lockup logo intended for rendering on dark backgrounds. |\n| `dark` | no | `ref:Asset` | Dark-colored full-lockup logo intended for rendering on light backgrounds. |\n| `stacked` | no | `ref:Asset` | Stacked vertical logo lockup, suited to portrait or square brand-mark slots. |\n| `stackedLight` | no | `ref:Asset` | Light-colored stacked logo variant intended for rendering on dark backgrounds. |\n| `stackedDark` | no | `ref:Asset` | Dark-colored stacked logo variant intended for rendering on light backgrounds. |\n| `icon` | no | `ref:Asset` | Default icon, mark, or symbol without wordmark. Useful for tight spaces such as footers, badges, and slide-corner marks. |\n| `iconLight` | no | `ref:Asset` | Light-colored icon variant intended for rendering on dark backgrounds. |\n| `iconDark` | no | `ref:Asset` | Dark-colored icon variant intended for rendering on light backgrounds. |\n| `wordmark` | no | `ref:Asset` | Default wordmark: the organization name set in branded typography, without icon. |\n| `wordmarkLight` | no | `ref:Asset` | Light-colored wordmark variant intended for rendering on dark backgrounds. |\n| `wordmarkDark` | no | `ref:Asset` | Dark-colored wordmark variant intended for rendering on light backgrounds. |\n\n\n### Watermark\n\n- Type: `object`\n- Required fields: `opacity`\n- Purpose: Decorative watermark image and rendering options. Use design.watermark = false to disable an inherited watermark.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `src` | no | `string` | Source for the watermark image. |\n| `opacity` | yes | `number` | Watermark opacity from 0 (fully transparent) to 1 (fully opaque). |\n\n\n### HeaderFooter\n\n- Type: `object`\n- Required fields: none\n- Purpose: Repeated header or footer content split into left, center, and right zones. Header/footer content is slide furniture, separate from the main slide content payloads.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `left` | no | `ref:HeaderFooterItem` | Left-aligned header/footer content. |\n| `center` | no | `ref:HeaderFooterItem` | Centered header/footer content. |\n| `right` | no | `ref:HeaderFooterItem` | Right-aligned header/footer content. |\n\n\n### HeaderFooterItem\n\n- Type: `object`\n- Required fields: none\n- Purpose: One header/footer zone. Every configured field renders; fields in one zone stack top to bottom in the order image, text, organization, section, slide number, date. Put a date and a slide number in different zones to keep each on the zone's single line.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `text` | no | `string` | Literal text rendered in this zone. |\n| `image` | no | `ref:Asset` | Generic image rendered in this zone, such as a logo, partner mark, certification badge, or icon. |\n| `slideNumber` | no | `boolean` | Whether to render the current slide number in this zone. PPTX export writes it as a live slide-number field. |\n| `slideNumberFormat` | no | `string` | Template for the slide number when slideNumber is true. {current} is the displayed slide number (a live field in PPTX); {total} is the number of slides in the rendered or exported deck, written as fixed text because P... |\n| `date` | no | `oneOf:boolean / string` | true renders the current date: the renderer or exporter must be given today's ISO date by its host (core never reads a clock), and PPTX export writes a live date field that PowerPoint updates. A string is fixed: with... |\n| `dateFormat` | no | `string` | Date pattern for date. Tokens: yyyy (2026), yy (26), MMMM (April), MMM (Apr), MM (04), M (4), dd (09), d (9), EEEE (Thursday), EEE (Thu). Text in single quotes and other non-letter characters are literal. Month and we... |\n| `organization` | no | `boolean` | Whether to render the primary organization name from organization. |\n| `section` | no | `boolean` | Whether to render the current slide section label. |\n| `socials` | no | `boolean` | Whether to render the primary organization's social profiles from organization.socials, one line per platform in key order. A handle is formatted through the platform's socialPlatforms record (companyUrlPattern, else... |\n\n\n### Slide\n\n- Type: `object`\n- Required fields: none\n- Purpose: A single slide. Content can be authored as a full-slide root payload, or inside promoted named region keys such as 'left', 'center+right', and 'top:left'.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional stable identifier for the slide within the document. Use when another system needs to reference a slide across edits, comments, generation state, exports, or narrative tooling. Slide order is defined by the s... |\n| `type` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline` | Optional full-slide content kind. When omitted, engines infer the kind from root payload fields. |\n| `beat` | no | `oneOf:string / array<string>` | Optional reference to one or more narrative beats (each value matches an id from narrative.beats or the resolved template). A single string declares the slide's primary beat; an array declares that one slide covers mu... |\n| `layout` | no | `string` | Optional slide layout reference. Resolves to the 'id' of a 'layouts' catalog record. When omitted, engines infer a layout from the slide's root payload or promoted region keys. Accepts a bare id (lowercase kebab-case,... |\n| `title` | no | `string` | Slide-level title content. When the resolved layout exposes a 'title' placeholder, the engine renders this value there. |\n| `subtitle` | no | `string` | Slide-level subtitle or supporting line. When the resolved layout exposes a 'subtitle' placeholder, the engine renders this value there. |\n| `tag` | no | `string` | Small slide-level label or badge. When the resolved layout exposes a 'tag' placeholder, the engine renders this value there. |\n| `text` | no | `oneOf:string / array<ref:TextRun>` | Full-slide text payload. Use a string for plain text or TextRun[] for inline rich text. TextRun items may be plain strings or formatted run objects. |\n| `items` | no | `array<ref:ListItem>` | Full-slide generic list payload. Presence of this field infers type 'list'. At slide root, multiple content payload kinds with no explicit type, blocks, or regions are accepted as shorthand for layout-agnostic blocks. |\n| `bullets` | no | `array<ref:BulletItem>` | Full-slide text-style bullet payload. Presence of this field infers type 'text'. |\n| `image` | no | `ref:Asset` | Full-slide image source. Presence of this field infers type 'image'. |\n| `video` | no | `ref:Asset` | Full-slide video source. Presence of this field infers type 'video'. |\n| `chart` | no | `ref:Chart` | Full-slide chart payload. Presence of this field infers type 'chart'. |\n| `table` | no | `ref:Table` | Full-slide table payload. Presence of this field infers type 'table'. |\n| `code` | no | `oneOf:string / ref:Code` | Full-slide code payload. A string is shorthand for { \"source\": value }; object form carries optional syntax language and filename metadata. |\n| `metric` | no | `oneOf:string / number / ref:Metric` | Full-slide metric payload. A string or number is shorthand for { \"value\": value }; object form carries optional label, description, unit, delta, and trend metadata. Numeric values remain numbers; renderers format them... |\n| `quote` | no | `oneOf:string / ref:Quote` | Full-slide quote payload. A string is shorthand for { \"text\": value }; object form carries optional attribution and source metadata. Presence of this field infers type 'quote'. |\n| `timeline` | no | `ref:Timeline` | Full-slide timeline payload. An array is shorthand for { \"events\": value }; object form carries optional name and description metadata. Presence of this field infers type 'timeline'. |\n| `blocks` | no | `array<ref:ContentPayload>` | Layout-agnostic content blocks rendered together as a composed payload when exact placement is unspecified. At slide root, multiple content payload kinds with no explicit type, blocks, or regions are accepted as short... |\n| `design` | no | `ref:Design` | Slide-level design applied on top of the deck-wide design. |\n| `left` | no | `ref:ContentPayload` | |\n| `center` | no | `ref:ContentPayload` | |\n| `right` | no | `ref:ContentPayload` | |\n| `left+center` | no | `ref:ContentPayload` | |\n| `center+right` | no | `ref:ContentPayload` | |\n| `left+center+right` | no | `ref:ContentPayload` | |\n| `top` | no | `ref:ContentPayload` | |\n| `middle` | no | `ref:ContentPayload` | |\n| `bottom` | no | `ref:ContentPayload` | |\n| `top+middle` | no | `ref:ContentPayload` | |\n| `middle+bottom` | no | `ref:ContentPayload` | |\n| `top+middle+bottom` | no | `ref:ContentPayload` | |\n| `top:left` | no | `ref:ContentPayload` | |\n| `top:center` | no | `ref:ContentPayload` | |\n| `top:right` | no | `ref:ContentPayload` | |\n| `top:left+center` | no | `ref:ContentPayload` | |\n| `top:center+right` | no | `ref:ContentPayload` | |\n| `top:left+center+right` | no | `ref:ContentPayload` | |\n| `middle:left` | no | `ref:ContentPayload` | |\n| `middle:center` | no | `ref:ContentPayload` | |\n| `middle:right` | no | `ref:ContentPayload` | |\n| `middle:left+center` | no | `ref:ContentPayload` | |\n| `middle:center+right` | no | `ref:ContentPayload` | |\n| `middle:left+center+right` | no | `ref:ContentPayload` | |\n| `bottom:left` | no | `ref:ContentPayload` | |\n| `bottom:center` | no | `ref:ContentPayload` | |\n| `bottom:right` | no | `ref:ContentPayload` | |\n| `bottom:left+center` | no | `ref:ContentPayload` | |\n| `bottom:center+right` | no | `ref:ContentPayload` | |\n| `bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `top+middle:left` | no | `ref:ContentPayload` | |\n| `top+middle:center` | no | `ref:ContentPayload` | |\n| `top+middle:right` | no | `ref:ContentPayload` | |\n| `top+middle:left+center` | no | `ref:ContentPayload` | |\n| `top+middle:center+right` | no | `ref:ContentPayload` | |\n| `top+middle:left+center+right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left` | no | `ref:ContentPayload` | |\n| `middle+bottom:center` | no | `ref:ContentPayload` | |\n| `middle+bottom:right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left+center` | no | `ref:ContentPayload` | |\n| `middle+bottom:center+right` | no | `ref:ContentPayload` | |\n| `middle+bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:center` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left+center` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:center+right` | no | `ref:ContentPayload` | |\n| `top+middle+bottom:left+center+right` | no | `ref:ContentPayload` | |\n| `notes` | no | `string` | Speaker notes shown in presenter view. |\n| `section` | no | `string` | PowerPoint-style slide section label. Consecutive slides with the same value belong to the same section in presenter view, outlines, and PowerPoint section-aware exports. |\n| `hidden` | no | `boolean` | Whether the slide is hidden from the presented sequence. |\n| `composition` | no | `ref:Composition` | |\n| `extensions` | no | `object` | Custom data passthrough for agent workflows at slide scope; ignored by the engine but preserved across read/write round-trips. Use for review state, generation provenance, or authoring conventions such as { \"authoring... |\n\n\n### ContentPayload\n\n- Type: `allOf:schema + schema + schema + schema + schema + schema + schema + schema + schema + schema + schema + schema`\n- Required fields: none\n- Purpose: A content leaf or recursively composed group. A group contains blocks and optional composition; it cannot mix blocks with leaf payload fields. Groups may nest up to 32 levels.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `id` | no | `string` | Optional stable identifier for this payload, unique among slide and payload ids in the document. Use when another system needs to address the payload across edits patch-style agent edits, comments, review state, or ge... |\n| `extensions` | no | `object` | Custom data passthrough for agent workflows at payload scope; ignored by the engine but preserved across read/write round-trips. |\n| `type` | no | `enum:text \\| list \\| image \\| chart \\| table \\| video \\| code \\| metric \\| quote \\| timeline \\| group` | Optional content kind. When omitted, engines infer the kind from the fields present. |\n| `text` | no | `oneOf:string / array<ref:TextRun>` | Text payload. Use a string for plain text or TextRun[] for inline rich text. TextRun items may be plain strings or formatted run objects. |\n| `items` | no | `array<ref:ListItem>` | Generic list payload. Each item is either a plain string, a TextRun[] rich text sequence, or a ListItem object. List nesting uses item.level rather than nested content payloads. |\n| `bullets` | no | `array<ref:BulletItem>` | Text-style bullet payload. Presence of this field infers type 'text'. |\n| `image` | no | `ref:Asset` | Source for an image item. |\n| `video` | no | `ref:Asset` | Source for a video item. |\n| `chart` | no | `ref:Chart` | Chart payload. Presence of this field infers type 'chart'. |\n| `table` | no | `ref:Table` | Table payload. Presence of this field infers type 'table'. |\n| `code` | no | `oneOf:string / ref:Code` | Code payload. A string is shorthand for { \"source\": value }; object form carries optional syntax language and filename metadata. |\n| `metric` | no | `oneOf:string / number / ref:Metric` | Metric payload. A string or number is shorthand for { \"value\": value }; object form carries optional label, description, unit, delta, and trend metadata. Numeric values remain numbers; renderers format them for display. |\n| `quote` | no | `oneOf:string / ref:Quote` | Quote payload. A string is shorthand for { \"text\": value }; object form carries optional attribution and source metadata. |\n| `timeline` | no | `ref:Timeline` | Timeline payload ordered by narrative or chronology. |\n| `blocks` | no | `array<ref:ContentPayload>` | Ordered children of a group. Each child is a leaf or another group. |\n| `composition` | no | `ref:Composition` | Arrangement within this group. Only minFontSize and overflow inherit from the parent; strict overflow cannot be weakened. |\n\n\n### Quote\n\n- Type: `object`\n- Required fields: `text`\n- Purpose: Quote content with optional attribution metadata. Use 'text' for the quoted text, 'attribution' for the credited person or organization, and 'source' for a citation or URL. A string value in a quote field is shorthand for { \"text\": value }.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `text` | yes | `string` | Quoted text. |\n| `attribution` | no | `string` | Person or organization credited for the quote. |\n| `source` | no | `string` | Optional quote source, citation, or URL. |\n\n\n### Code\n\n- Type: `object`\n- Required fields: `source`\n- Purpose: Code content with optional rendering metadata. Use 'source' for the code text, 'language' for syntax highlighting, and 'filename' when the rendered block should show a file label. A string value in a code field is shorthand for { \"source\": value }.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `source` | yes | `string` | Source code text to display. |\n| `language` | no | `string` | Language identifier used for syntax highlighting. |\n| `filename` | no | `string` | Optional file label shown with the code block. |\n\n\n### Metric\n\n- Type: `object`\n- Required fields: `value`\n- Purpose: Metric content with optional display metadata. Use 'value' for the primary value, 'label' for the metric name, 'description' for supporting context, 'unit' for a suffix/currency marker, 'delta' for change, and 'trend' for direction. A string or number value in a metric field is shorthand for { \"value\": value }; numeric values remain numbers and are formatted by renderers.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `value` | yes | `oneOf:string / number` | Primary metric value. |\n| `label` | no | `string` | Metric label. |\n| `description` | no | `string` | Optional supporting context for the metric. |\n| `unit` | no | `string` | Metric unit, suffix, or currency marker. |\n| `delta` | no | `oneOf:string / number` | Metric change value. |\n| `trend` | no | `enum:up \\| down \\| flat` | Metric trend direction. |\n\n\n### Timeline\n\n- Type: `oneOf:array<ref:TimelineEvent> / object`\n- Required fields: none\n- Purpose: Timeline content. An array is shorthand for { \"events\": value }; object form carries optional name and description metadata.\n\n_No named properties._\n\n\n### TimelineEvent\n\n- Type: `object`\n- Required fields: `what`\n- Purpose: A single event inside a timeline content payload.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `when` | no | `string` | Event time, date, or sequence label. Use ISO-like values when possible, but human labels are allowed for quarters, eras, and relative milestones. |\n| `what` | yes | `string` | Short event label. |\n| `description` | no | `string` | Optional event detail. |\n\n\n### ListItem\n\n- Type: `oneOf:string / array<ref:TextRun> / object`\n- Required fields: none\n- Purpose: A flat item inside a list. Strings cover the common case, TextRun[] supports inline rich text without an object wrapper, and object form adds description and nesting depth without creating nested slide content payloads.\n\n_No named properties._\n\n\n### BulletItem\n\n- Type: `oneOf:string / array<ref:TextRun> / object`\n- Required fields: none\n- Purpose: A flat bullet item. Strings cover the common case, TextRun[] supports inline rich text without an object wrapper, and object form adds nesting depth without list-item descriptions.\n\n_No named properties._\n\n\n### TextRun\n\n- Type: `oneOf:string / object`\n- Required fields: none\n- Purpose: A contiguous run of text. Strings cover unformatted spans; object form adds character formatting.\n\n_No named properties._\n\n\n### Chart\n\n- Type: `object`\n- Required fields: `type`, `data`\n- Purpose: Chart content. The chart object keeps chart-specific fields together so slides and regions do not expose loose chart fields.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `type` | yes | `string` | Chart type id. Resolves to the id of a chartTypes catalog record; renderers map that record through mappings.openxml and any renderer-specific mapping they understand. The bundled catalog covers the chart types Aspose... |\n| `data` | yes | `oneOf:ref:ChartData / ref:ChartDataSource` | Chart data. Inline data uses a tabular columns/rows shape; renderers convert rows to chart series internally. |\n\n\n### Table\n\n- Type: `object`\n- Required fields: `rows`\n- Purpose: Table content. Columns are optional; rows are the only required field.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `columns` | no | `array<oneOf:string / array<ref:TextRun> / ref:StyledTableCell / null>` | Optional column labels. Labels may be strings, rich runs or styled cell objects. Null is an empty label or a placeholder covered by a preceding column span. |\n| `rows` | yes | `array<array<ref:TableCell>>` | Two-dimensional table row data; each row aligns by index with columns when columns are supplied. |\n\n\n### ChartData\n\n- Type: `object`\n- Required fields: `columns`, `rows`\n- Purpose: Inline tabular data driving a chart. The first column usually supplies category/x-axis labels; subsequent columns are plotted measures unless a chart type or renderer maps them differently.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `columns` | yes | `array<string>` | Ordered column labels for the chart data table. |\n| `rows` | yes | `array<array<ref:ChartDataCell>>` | Tabular chart rows. Each row aligns by index with columns. |\n\n\n### ChartDataSource\n\n- Type: `object`\n- Required fields: `src`\n- Purpose: Chart data sourced from an asset reference, URL, data URI, relative path, or local path such as CSV, TSV, JSON, or XLSX. The source is interpreted as a table; optional columns select or order fields from that table.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `src` | yes | `string` | Data source. Use 'asset:<id>' to reference the top-level assets registry, or provide an HTTPS URL, data URI, relative path, or local filesystem path. |\n| `sheet` | no | `string` | Optional sheet name or table name for spreadsheet-like assets. |\n| `range` | no | `string` | Optional A1-style range or engine-defined range selector for spreadsheet-like assets. |\n| `columns` | no | `array<string>` | Optional ordered columns or fields to read from the source. When omitted, renderers may use the source's own header row or schema. |\n\n\n### ChartDataCell\n\n- Type: `oneOf:string / number / boolean / null`\n- Required fields: none\n- Purpose: A cell in inline chart data.\n\n_No named properties._\n\n\n### TableCell\n\n- Type: `oneOf:ref:TableCellValue / ref:StyledTableCell`\n- Required fields: none\n- Purpose: A scalar, rich-run array, or styled/spanning cell object. Existing scalar and rich forms remain valid.\n\n_No named properties._\n\n\n### TableCellValue\n\n- Type: `oneOf:string / number / boolean / null / array<ref:TextRun>`\n- Required fields: none\n- Purpose: A scalar table value or canonical rich text runs, without cell decoration or geometry.\n\n_No named properties._\n\n\n### StyledTableCell\n\n- Type: `object`\n- Required fields: `value`\n- Purpose: A cell with explicit visual style or merged geometry. Its position remains its array column index; use null placeholders for every covered grid position.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `value` | yes | `ref:TableCellValue` | Editable cell content; styling and spans do not change its scalar type or rich runs. |\n| `style` | no | `ref:TableCellStyle` | |\n| `colSpan` | no | `integer` | Number of grid columns covered, starting at this cell. Covered positions must contain null. Default 1. |\n| `rowSpan` | no | `integer` | Number of grid rows covered, starting at this cell. Covered positions must contain null. Header cells cannot span into body rows. Default 1. |\n\n\n### TableCellStyle\n\n- Type: `object`\n- Required fields: none\n- Purpose: Cell appearance. Sizes use reference pixels at a 720-pixel canvas short edge and scale with the slide.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `fill` | no | `ref:ColorRef` | Cell background: a hex color, a color-scheme slot or role name, or a 'var:<id>' variable reference. Eight-digit hex colors include alpha; #00000000 is transparent. |\n| `color` | no | `ref:ColorRef` | Default text color, overridden by individual rich run colors. Accepts a hex color, a color-scheme slot or role name, or a 'var:<id>' variable reference. |\n| `align` | no | `enum:left \\| center \\| right` | Horizontal text alignment inside the cell. |\n| `verticalAlign` | no | `enum:top \\| middle \\| bottom` | Vertical alignment inside the padded cell box. |\n| `padding` | no | `ref:TableCellPadding` | |\n| `borders` | no | `object` | Independent cell edges. Omitted edges retain the table theme border; width 0 removes an edge. |\n\n\n### TableCellPadding\n\n- Type: `object`\n- Required fields: none\n- Purpose: Text insets in reference pixels. Defaults: top 8, right 10, bottom 4, left 10.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `top` | no | `number` | |\n| `right` | no | `number` | |\n| `bottom` | no | `number` | |\n| `left` | no | `number` | |\n\n\n### TableCellBorder\n\n- Type: `object`\n- Required fields: `color`, `width`\n- Purpose: One explicit cell border.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `color` | yes | `ref:ColorRef` | Border color: a hex color, a color-scheme slot or role name, or a 'var:<id>' variable reference. Eight-digit hex colors include alpha; #00000000 is transparent. |\n| `width` | yes | `number` | Border width in reference pixels; 0 removes this edge. |\n| `dash` | no | `enum:solid \\| dash \\| dot` | Default solid. |\n\n\n### Catalogs\n\n- Type: `object`\n- Required fields: none\n- Purpose: Catalog overrides for the in-document references. Every property is optional. The default catalog for a kind lives at https://www.pptx.gallery/<kind> (e.g. https://www.pptx.gallery/narratives, https://www.pptx.gallery/themes). For each kind, declaring a 'source' replaces the default registry and/or 'records' adds inline records that take precedence over anything fetched from a source. Resolution order for any reference (e.g. narrative, design.theme): inline catalogs.<kind>.records[] catalogs....\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `narratives` | no | `ref:CatalogEntry` | Catalog of narrative templates. Records validate against https://openpresentation.org/schema/opf-narrative/v1. Default source: https://www.pptx.gallery/narratives. |\n| `themes` | no | `ref:CatalogEntry` | Catalog of themes. Records validate against https://openpresentation.org/schema/opf-theme/v1. Default source: https://www.pptx.gallery/themes. |\n| `colorSchemes` | no | `ref:CatalogEntry` | Catalog of color schemes. Records validate against https://openpresentation.org/schema/opf-color-scheme/v1. Default source: https://www.pptx.gallery/color-schemes. |\n| `fontSchemes` | no | `ref:CatalogEntry` | Catalog of font schemes. Records validate against https://openpresentation.org/schema/opf-font-scheme/v1. Default source: https://www.pptx.gallery/font-schemes. |\n| `languages` | no | `ref:CatalogEntry` | Catalog of languages. Records validate against https://openpresentation.org/schema/opf-language/v1. Default source: https://www.pptx.gallery/languages. |\n| `layouts` | no | `ref:CatalogEntry` | Catalog of slide layouts. Records validate against https://openpresentation.org/schema/opf-layout/v1. Default source: https://www.pptx.gallery/layouts. |\n| `chartTypes` | no | `ref:CatalogEntry` | Catalog of chart types. Records validate against https://openpresentation.org/schema/opf-chart-type/v1. Default source: https://www.pptx.gallery/chart-types. |\n| `tones` | no | `ref:CatalogEntry` | Catalog of presentation tones. Records validate against https://openpresentation.org/schema/opf-tone/v1. Default source: https://www.pptx.gallery/tones. Referenced from tone. |\n| `purposes` | no | `ref:CatalogEntry` | Catalog of presentation purposes. Records validate against https://openpresentation.org/schema/opf-purpose/v1. Default source: https://www.pptx.gallery/purposes. Referenced from purpose. |\n| `audiences` | no | `ref:CatalogEntry` | Catalog of presentation audiences. Records validate against https://openpresentation.org/schema/opf-audience/v1. Default source: https://www.pptx.gallery/audiences. Referenced from audience. |\n| `socialPlatforms` | no | `ref:CatalogEntry` | Catalog of social-media platforms. Records validate against https://openpresentation.org/schema/opf-social-platform/v1. Default source: https://www.pptx.gallery/social-platforms. Referenced via the property keys of an... |\n\n\n### CatalogEntry\n\n- Type: `object`\n- Required fields: none\n- Purpose: A catalog override for one record kind. 'source' replaces the default registry; 'records' adds inline records that take precedence over anything fetched from a source. Either or both may be provided; both omitted means the kind uses its default catalog.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `source` | no | `oneOf:ref:CatalogSource / array<ref:CatalogSource>` | Single source or an ordered search path of sources. When omitted, the engine falls back to https://www.pptx.gallery/<kind>. |\n| `records` | no | `array<object>` | Inline catalog records embedded in this OPF document. Each record validates against the kind's companion schema (e.g. https://openpresentation.org/schema/opf-narrative/v1 for narratives). Inline records win over anyth... |\n\n\n### CatalogSource\n\n- Type: `string`\n- Required fields: none\n- Purpose: Catalog source location. Accepts: - A bare URL pointing at a catalog directory (e.g. 'https://acme.com/decks/narratives'); record ids resolve to '<base>/<id>.json'. - A URL pointing at an index file (e.g. 'https://acme.com/decks/narratives/index.json'); records are resolved relative to the index file's directory and the index entries describe what's available. - A package reference of the form 'pkg:<package>[/<subpath>]'; resolved through a locally-installed package on the engine's package path.\n\n_No named properties._\n\n\n### Composition\n\n- Type: `object`\n- Required fields: none\n- Purpose: Portable dynamic composition. Slide fields override the resolved layout. Nested groups arrange their children independently, inheriting only minFontSize and overflow. Explicit promoted regions retain their positions.\n\n| Field | Required | Type | Notes |\n| --- | --- | --- | --- |\n| `mode` | no | `enum:auto \\| grid \\| row \\| column` | auto chooses a grid from available space and content; grid uses columns; row and column use one horizontal or vertical track. |\n| `columns` | no | `integer` | Column count for grid. In auto mode this caps the number of columns. |\n| `gap` | no | `number` | Space between cells as a fraction of the container short edge (canvas at slide root). Default 0.03333333333333333. |\n| `padding` | no | `number` | Inset as a fraction of the container short edge. Default 0.08 on a slide, 0 inside a group. |\n| `weights` | no | `array<number>` | Relative track sizes: columns for row/grid/auto, rows for column. Omitted tracks have weight 1; extra weights are ignored. |\n| `minFontSize` | no | `number` | Minimum readable text size in reference pixels at a 720-pixel canvas short edge. Default 16. Overflow is diagnosed when text cannot fit at this size. |\n| `overflow` | no | `enum:warn \\| error` | warn returns diagnostics for content that does not fit; error rejects layout. Content is never silently removed. Default warn. |\n"
176
230
  },
177
231
  {
178
232
  "slug": "security-2026-09-09",
@@ -186,11 +240,17 @@ var docsData = Object.freeze([
186
240
  "title": "OpenPresentation status \u2014 September 15, 2026",
187
241
  "markdown": "# OpenPresentation status \u2014 September 15, 2026\n\nThe public JSON/slide demo and reusable authoring foundation are available.\nShared headers/footers are validated on main. Four unfinished shaping prototypes\nare preserved on remote archive branches and tracked in the roadmap; their\noriginal PRs conclude with documentation/evidence changes only.\n\nRead the [current handoff](handoff-2026-09-15.md),\n[next-agent prompt](next-agent-prompt-2026-09-15.md) and\n[developer adoption roadmap](plans/developer-adoption-20260915.md).\nEarlier dated checkpoints are historical evidence, not current release claims.\n\n| Area | Current state |\n| --- | --- |\n| Public demo | Home/playground support editable OPF JSON, live preview, preview edits back to JSON, contextual catalog choices and normal code-editor assistance. Appearance is preserved. |\n| Published packages | Core0.10.0, renderer0.8.0, editor0.7.0, PPTX0.8.0 and CLI0.8.0 were confirmed in the registry. This final cleanup publishes no additional versions. |\n| Developer foundation | Schemas/catalogs, six agent skills, contextual lint/contracts, source-preserving edits/undo, composition/pagination, preview and supported exports exist. Feature and platform limits apply. |\n| Shared headers/footers | Core79, renderer20, editor17 and PPTX34 are merged with passing main CI. This increment needs new coordinated versions and consumer adoption. |\n| Font shaping/prepared editing | Core83, renderer21, editor21 and PPTX35 retain complete prototypes on archive branches. Final PR diffs contain roadmap/evidence only. Linux native-width failures and an editor packed-test assertion remain recorded, not waived. |\n| Native PowerPoint | Acceptance is separately tracked in core issue87. Serialization and self-import do not certify Office. |\n| Production checks | Latest retained deployed checks passed 26 main-site, five gallery and ten pptx.dev workflows. See the handoff for commits/evidence. |\n\nThe original twenty-PR queue comprises eleven accepted dependency updates,\nfour accepted furniture increments, four roadmap-only conclusions and one\nclosed Node26-types update because the ecosystem targets Node24. Check the\nlinked PR states for final merge/check receipts; merging a roadmap does not\nrelease its archived prototype.\n\nNext: release the accepted increment, provide one independently installable\ndeveloper example with current API/version docs, and adopt shared behavior\nacross the public sites. Broader font/IME/bidi coverage, native Office evidence,\nlayout repair and full visual-editor coverage remain roadmap work. Selectable\nvector PDF and general SVG/Mermaid follow font reliability.\n"
188
242
  },
243
+ {
244
+ "slug": "status-2026-09-16",
245
+ "file": "docs/status-2026-09-16.md",
246
+ "title": "OpenPresentation status \u2014 September 16, 2026",
247
+ "markdown": "# OpenPresentation status \u2014 September 16, 2026\n\n> Historical 16\u201317 September checkpoint, superseded by the [21 September handoff](handoff-2026-09-21.md) and [current compatibility matrix](compatibility-matrix.md). The 0.10.1 train below is not the current install recommendation.\n\nTreat [status-2026-09-15](status-2026-09-15.md) as the previous checkpoint.\nLive npm is **newer** than that file: core 0.10.1, renderer/PPTX/CLI 0.8.1,\neditor 0.7.1, all Node 24. Production issue88 features were verified\n**17 September 2026**. The developer-ready milestone is **not** complete.\n\n| Area | Current state |\n| --- | --- |\n| Published packages | `@openpresentation/opf@0.10.1`, `opf-render@0.8.1`, `opf-editor@0.7.1`, `opf-pptx@0.8.1`, `cli@0.8.1` |\n| Shared headers/footers | In that published set (`furniture-flow-v2`). Not a pending unpublished increment. |\n| PPTX furniture | Tagged slide shapes (`OPF_FURNITURE_V1`), not native Office Header/Footer (`p:hf` / notes master). Native HF is [issue 87](https://github.com/OpenPresentation/opf/issues/87) only. |\n| Developer starting path | [PR 91](https://github.com/OpenPresentation/opf/pull/91) **merged**. [Quickstart](quickstart.md) and [compatibility matrix](compatibility-matrix.md) are on `main`. Prove with `pnpm test:developer-quickstart` (fresh registry install). Fixture: `docs/quickstart/developer-quickstart.opf.json`, not the 126-deck examples catalog. |\n| Public-site adoption | **Live on production** (2026-09-17): Inspector overlay/json-options on `www.pptx.dev` (`17de6da`, deploy `dpl_6QHXPJ5QLTYXHNqtPoByqxHxbqHx`); gallery Playground+Editor links on `www.pptx.gallery` (`c7d3754`, `dpl_2SyhP4oX23ZBjsnVec7WKKaSF8xU`); Header & footer playground example on `www.openpresentation.org` (`ea0d032`, `dpl_GqqrJyxmwT5JCeQY3m9Tw2SDRN5R`). GitHub [issue 88](https://github.com/OpenPresentation/opf/issues/88) remains **open**. Geometry drafts and homepage-renderer are **not** shipped. |\n| Renderer native widths | Still [opf-render#24](https://github.com/OpenPresentation/opf-render/issues/24). Deferred; 0.1px gate unchanged. |\n| Native PowerPoint | Still [issue 87](https://github.com/OpenPresentation/opf/issues/87). Serialization/`toPptx` are not Office acceptance. Do not implement `p:hf`. |\n| Archived shaping | Remote `codex/archive-shaping-20260915` branches; not in npm |\n\nPDF remains raster-backed. The CLI does not render. An empty PR queue does not\nfinish the ecosystem objective.\n"
248
+ },
189
249
  {
190
250
  "slug": "table-text-colors",
191
251
  "file": "docs/table-text-colors.md",
192
252
  "title": "Inherited table text colors",
193
- "markdown": "# Inherited table text colors\n\nThe unpublished shared-metric integration branches use one core rule for inherited table text colors in SVG and editable PowerPoint cells. After resolving the cell fill, keep the inherited text color when its unrounded contrast is at least 4.5:1. Otherwise choose the higher-contrast black or white. This covers pale headers and dark body-cell fills without changing the source document.\n\nAn explicit cell `style.color` or rich-text run `color` remains authoritative, including a deliberately low-contrast color. Translucent fills and unresolved colors keep the inherited preference: their actual backdrop must be known before assessing contrast. This rule does not alter fills, borders, fonts, layout or metadata.\n\nCore exports `colorContrast(foreground, background)` and `textColorForFill(fill, preferred)` from its root and `/composition` entrypoints. They accept opaque hexadecimal `#RGB`, `#RRGGBB` and `#RRGGBBFF` colors. `colorContrast` returns `undefined` for unsupported or translucent colors. Callers must apply explicit text-color overrides before invoking the fallback.\n\nThe ratio uses [W3C's sRGB relative luminance definition](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). Passing this narrow color check is not a WCAG certification, a readability guarantee, a browser/native raster equivalence result or an arbitrary PowerPoint round-trip claim. Source tests check actual SVG attributes and native OOXML text colors, including inherited and explicit rich-text colors. A separate six-slide real PowerPoint test passes on Node 20.20.2 and 24.20.0: all 48 original/reopened cell observations and 624 character-color observations match per runtime. Native rasters remain distinct evidence from browser rendering.\n"
253
+ "markdown": "# Inherited table text colors\n\nThe published core 0.11.0, renderer 0.9.0 and PPTX 0.9.1 train uses one core rule for inherited table text colors in SVG and editable PowerPoint cells. After resolving the cell fill, keep the inherited text color when its unrounded contrast is at least 4.5:1. Otherwise choose the higher-contrast black or white. This covers pale headers and dark body-cell fills without changing the source document.\n\nAn explicit cell `style.color` or rich-text run `color` remains authoritative, including a deliberately low-contrast color. Translucent fills and unresolved colors keep the inherited preference: their actual backdrop must be known before assessing contrast. This rule does not alter fills, borders, fonts, layout or metadata.\n\nCore exports `colorContrast(foreground, background)` and `textColorForFill(fill, preferred)` from its root and `/composition` entrypoints. They accept opaque hexadecimal `#RGB`, `#RRGGBB` and `#RRGGBBFF` colors. `colorContrast` returns `undefined` for unsupported or translucent colors. Callers must apply explicit text-color overrides before invoking the fallback.\n\nThe ratio uses [W3C's sRGB relative luminance definition](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). Passing this narrow color check is not a WCAG certification, a readability guarantee, a browser/native raster equivalence result or an arbitrary PowerPoint round-trip claim. Source tests check actual SVG attributes and native OOXML text colors, including inherited and explicit rich-text colors. A [recorded six-slide real PowerPoint test](handoff-2026-09-08.md) passed on Node 20.20.2 and 24.20.0 at its source checkpoint: all 48 original/reopened cell observations and 624 character-color observations match per runtime. Those historical native rasters remain distinct from current-package and browser acceptance.\n"
194
254
  }
195
255
  ]);
196
256
  var docsRaw = docsData;