slotdeck 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +86 -6
  2. package/dist/deck/alternatives.d.ts +34 -0
  3. package/dist/deck/alternatives.js +370 -0
  4. package/dist/deck/alternatives.js.map +1 -0
  5. package/dist/deck/design.d.ts +40 -0
  6. package/dist/deck/design.js +112 -0
  7. package/dist/deck/design.js.map +1 -0
  8. package/dist/deck/validate.d.ts +18 -0
  9. package/dist/deck/validate.js +38 -2
  10. package/dist/deck/validate.js.map +1 -1
  11. package/dist/index.d.ts +22 -1
  12. package/dist/index.js +13 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/node.d.ts +7 -0
  15. package/dist/node.js +29 -0
  16. package/dist/node.js.map +1 -1
  17. package/dist/plan/catalog.js +3 -1
  18. package/dist/plan/catalog.js.map +1 -1
  19. package/dist/plan/edit.js +6 -2
  20. package/dist/plan/edit.js.map +1 -1
  21. package/dist/plan/plan.d.ts +10 -0
  22. package/dist/plan/plan.js +8 -0
  23. package/dist/plan/plan.js.map +1 -1
  24. package/dist/plan/review.d.ts +43 -0
  25. package/dist/plan/review.js +141 -0
  26. package/dist/plan/review.js.map +1 -0
  27. package/dist/plan/write.d.ts +5 -0
  28. package/dist/plan/write.js +11 -4
  29. package/dist/plan/write.js.map +1 -1
  30. package/dist/preview/pagus.d.ts +30 -0
  31. package/dist/preview/pagus.js +45 -0
  32. package/dist/preview/pagus.js.map +1 -0
  33. package/dist/preview/patterns.d.ts +27 -0
  34. package/dist/preview/patterns.js +35 -0
  35. package/dist/preview/patterns.js.map +1 -0
  36. package/dist/preview/provider.d.ts +30 -0
  37. package/dist/preview/provider.js +11 -0
  38. package/dist/preview/provider.js.map +1 -0
  39. package/dist/render/render.d.ts +18 -1
  40. package/dist/render/render.js +28 -8
  41. package/dist/render/render.js.map +1 -1
  42. package/dist/render/repeat.d.ts +17 -2
  43. package/dist/render/repeat.js +150 -4
  44. package/dist/render/repeat.js.map +1 -1
  45. package/dist/render/text.d.ts +6 -0
  46. package/dist/render/text.js +24 -0
  47. package/dist/render/text.js.map +1 -1
  48. package/dist/template/budgets.d.ts +33 -0
  49. package/dist/template/budgets.js +38 -0
  50. package/dist/template/budgets.js.map +1 -0
  51. package/dist/template/change.d.ts +18 -1
  52. package/dist/template/change.js +33 -3
  53. package/dist/template/change.js.map +1 -1
  54. package/dist/template/check.d.ts +3 -0
  55. package/dist/template/check.js +53 -3
  56. package/dist/template/check.js.map +1 -1
  57. package/dist/template/derive.d.ts +54 -0
  58. package/dist/template/derive.js +402 -0
  59. package/dist/template/derive.js.map +1 -0
  60. package/dist/template/draft.d.ts +7 -0
  61. package/dist/template/draft.js +17 -1
  62. package/dist/template/draft.js.map +1 -1
  63. package/dist/template/instruct.js +11 -5
  64. package/dist/template/instruct.js.map +1 -1
  65. package/dist/template/manifest.d.ts +22 -0
  66. package/dist/template/manifest.js +17 -1
  67. package/dist/template/manifest.js.map +1 -1
  68. package/dist/template/sample.d.ts +17 -0
  69. package/dist/template/sample.js +27 -2
  70. package/dist/template/sample.js.map +1 -1
  71. package/dist/text/fontkit.d.ts +18 -0
  72. package/dist/text/fontkit.js +91 -0
  73. package/dist/text/fontkit.js.map +1 -0
  74. package/dist/text/fonts.d.ts +45 -0
  75. package/dist/text/fonts.js +59 -0
  76. package/dist/text/fonts.js.map +1 -0
  77. package/dist/text/layout.d.ts +12 -0
  78. package/dist/text/layout.js +104 -0
  79. package/dist/text/layout.js.map +1 -0
  80. package/dist/text/measure.d.ts +85 -0
  81. package/dist/text/measure.js +369 -0
  82. package/dist/text/measure.js.map +1 -0
  83. package/dist/text/styles.d.ts +123 -0
  84. package/dist/text/styles.js +417 -0
  85. package/dist/text/styles.js.map +1 -0
  86. package/dist/tools.d.ts +12 -0
  87. package/dist/tools.js +32 -6
  88. package/dist/tools.js.map +1 -1
  89. package/docs/README.md +15 -5
  90. package/docs/api-reference.md +278 -8
  91. package/docs/changelog.md +135 -0
  92. package/docs/chat-tools.md +22 -5
  93. package/docs/deck-format.md +6 -0
  94. package/docs/design-checks.md +216 -0
  95. package/docs/generating.md +4 -1
  96. package/docs/layout-freedom.md +255 -3
  97. package/docs/manifest-format.md +19 -3
  98. package/docs/preview.md +217 -0
  99. package/docs/providers.md +1 -1
  100. package/docs/rendering.md +8 -0
  101. package/docs/template-studio.md +110 -11
  102. package/docs/text-fit.md +456 -0
  103. package/docs/troubleshooting.md +13 -2
  104. package/package.json +7 -1
package/docs/changelog.md CHANGED
@@ -3,6 +3,141 @@
3
3
  What changed in each version, newest first, with what to do when upgrading. The version you have installed is
4
4
  in `node_modules/slotdeck/package.json`.
5
5
 
6
+ ## 0.2.0
7
+
8
+ Two groups of additions. The first is about seeing and checking a deck: text measured with the template's
9
+ fonts, pictures of slides without PowerPoint, and design checks. The second is about more layouts from the
10
+ same template: variants the studio derives by itself, rows that fill their line, and other layouts for a
11
+ slide. Everything new is off until it is asked for.
12
+
13
+ ### Added
14
+
15
+ - **Text is measured with the template's fonts.** Load a template with font metrics
16
+ (`loadTemplate(pptx, manifest, { fonts })`) and text is broken into lines at each slot's typeface and size,
17
+ the way PowerPoint sets it. Validation and `renderDeck` then warn by the lines a text runs over
18
+ (`text takes 7 lines at 18 pt; the box holds 5 (2 lines too many). Text of about 290 characters fits.`), and
19
+ the planner sends a draft back to the model with that sentence. On the demo template every line broke at
20
+ the same word as in PowerPoint in all 1,153 text slots checked at the designed sizes, in English and in
21
+ Hebrew, and 699 of 700 slots had PowerPoint's line count with every font size halved. See
22
+ [text-fit.md](text-fit.md).
23
+ - **`fontkitMetrics(fontkit, files)`** builds the metrics from font files with the `fontkit` library, which
24
+ you install yourself: slotdeck does not depend on it. `FontMetrics` is the small interface behind it, for
25
+ using another library.
26
+ - **`templateFonts(template)`** lists the typefaces, weights and slants a template's text is set in, and
27
+ **`fontFiles(fontkit, folder, typefaces)`** from `slotdeck/node` finds their files in a font folder.
28
+ - **Shrinking text to fit.** `shrinkText: { to: 0.85, minSize: 10 }` on the manifest lets text that does not
29
+ fit be set smaller, in half points, down to the stated limit. `renderDeck` returns what it shrank in `shrunk`.
30
+ - **`textMeasurer(template)`** measures any text in any text slot: lines, capacity, overflow, the lines
31
+ themselves. `validateDeck` takes it as the `measure` option.
32
+ - **`measuredBudgets(template)`** says how many characters each text slot's box really holds and suggests a
33
+ budget, to set with `changeManifest`.
34
+ - **`checkManifest`** tries every text budget out when the template has fonts, and warns about a budget that
35
+ lets text run over its box. It also names typefaces that were not supplied.
36
+ - **`sampleDeck(template, { fit: 'box' })`** fills every text slot to what its box holds by measurement.
37
+ - **Slide previews.** `PreviewProvider` is a new interface for a renderer that turns a pptx into one picture
38
+ for each slide. slotdeck draws nothing itself and depends on no renderer. `pagusPreviews(core, renderer)` is
39
+ an adapter for the Pagus library, which you install yourself; it gives SVG for a browser to show, with
40
+ Hebrew and Arabic paragraphs marked right to left. `previewDeck` renders a deck and draws it,
41
+ `patternPreviews` makes a thumbnail for every pattern of a template, and `presentationTools` takes a
42
+ `preview` option: the pictures of every presentation it makes go to the store under `preview/<id>/` and to
43
+ the app in `ToolResult.previews`. No renderer tried reproduces PowerPoint exactly; what was found, for
44
+ English, Hebrew, tables and native charts, is in [preview.md](preview.md).
45
+ - **Design checks.** `checkDesign(template, deck)` lists problems of a deck that fits and still looks wrong,
46
+ from measurement alone: a box left almost empty, a card far shorter than its neighbours, a heading that ends
47
+ with one word on a line of its own. Every message says what length of text would put it right. See
48
+ [design-checks.md](design-checks.md).
49
+ - **Reviews.** The planner takes a `review` option: a second look at every draft, whose findings go back to
50
+ the writing model in the repair rounds. `designReview(template)` is the design checks as a review.
51
+ `visionReview({ template, llm, preview })` shows a model pictures of the slides; it needs a preview provider
52
+ that gives bitmap pictures, and it has been tested with a scripted model only, not run against a live one.
53
+ Nothing reviews a draft unless `review` is set. `presentationTools` takes the same option.
54
+ - **Variants the studio derives by itself.** A new `changeManifest` operation,
55
+ `{ op: 'derivePattern', pattern, variant }`, makes a second pattern from a pattern's slide and works out every
56
+ position and budget from the shapes of the slide. Variants: `mirror` (the picture and what sits beside it
57
+ change sides, including a photo that runs off the edge of the slide), `text-only` (the picture is left out
58
+ and the text widens over the room it leaves) and `without` (an optional text named in `slot` is left out and
59
+ what sits under it moves up). A variant the slide does not allow is refused with the reason. See "Variants
60
+ the code works out" in [layout-freedom.md](layout-freedom.md) and `derivePattern` in
61
+ [template-studio.md](template-studio.md).
62
+ - **`editManifest` understands requests for a variant**, such as "add a mirrored version of the photo slides".
63
+ The model names the variant; it still never sets a position.
64
+ - **`patternVariants(template, pattern)`** lists the variants a pattern allows, with the reason for each one it
65
+ does not, for a studio screen that offers them as buttons. `VARIANTS` is the list of variant names.
66
+ - **`variants` on `draftManifest`** adds the mirror image and the text-only version of every drafted pattern
67
+ that allows one. Off by default: see "Variants in a draft" in [template-studio.md](template-studio.md).
68
+ - **A slot can show a part of the design in another place.** A slot with `"empty": "keep"` and a `box` keeps
69
+ its shape's content and puts the shape at the box, and may point at any shape that is not inside a group,
70
+ a group as a whole included. `derivePattern` adds such slots (`moved1`, `moved2`, ...) for the shapes of a
71
+ variant that are not slots. See [manifest-format.md](manifest-format.md).
72
+ - **The demo template has mirrored photo patterns**: `text-and-photo-mirrored`, `photo-stat-mirrored` and
73
+ `list-with-photo-mirrored`, made with the new operation.
74
+ - **Rows that fill their line.** `widen` on a `repeat` lets the items of a row or grid grow wider when a line
75
+ holds fewer of them than the template draws, up to the factor given (`"widen": 1.5`), so that two or three
76
+ cards fill a row drawn for four. Backgrounds and text boxes widen; icons, pictures and circles keep their
77
+ size and their side of the item, also inside a group. The character budgets of the row's texts grow with
78
+ the items, in validation, in what the planner shows the model, and in `sampleDeck`. Set it by hand or with
79
+ `changeManifest(template, [{ op: 'setRepeat', pattern: '<id>', widen: 1.5 }])`; `editManifest` understands
80
+ requests such as "let two cards fill the row". See "Fewer items that fill the row" in
81
+ [layout-freedom.md](layout-freedom.md).
82
+ - **The demo template's `icon-cards` and `stat-cards` use `widen`**, so the example decks with three cards
83
+ or four figures fill their rows.
84
+ - **`slideAlternatives(manifest, deck, slide)`** lists the other patterns that can hold what one slide of a deck
85
+ says and returns the slide written for each: a row of cards as a list or as columns, a slide as its mirror
86
+ image, a list as paragraphs. Code only: no model call, and no word is changed. A photo or an icon the new
87
+ pattern has no place for is reported, and so is a text that is over its new budget. The chat tools do not
88
+ offer it yet. See "Another layout for the same slide" in [layout-freedom.md](layout-freedom.md).
89
+
90
+ - **Measuring follows the layout.** With fonts, the text of a row that widens its items is measured in the
91
+ box the renderer gives it at the slide's number of items, so validation, shrinking and the design checks
92
+ agree with the slide: text that fits two wide cards is accepted there and reported in four narrow ones. On
93
+ the demo template all 22 texts in widened cards and figures broke where PowerPoint broke them, in English
94
+ and in Hebrew. `checkManifest` tries the larger budgets of such a row in the wider boxes, and
95
+ `sampleDeck(template, { fit: 'box', items: 'min' })` fills them. A mirrored pattern, whose boxes only move
96
+ their shapes, is checked for empty boxes against the template's own text like the pattern it mirrors.
97
+
98
+ ### Changed
99
+
100
+ - `renderDeck` returns `shrunk` next to `pptx` and `warnings`. It is an empty array unless the manifest has
101
+ `shrinkText` and the template has fonts.
102
+ - `loadTemplate` takes a third, optional argument. `Template` has an optional `fonts` field.
103
+ - `storeKeys` has a `preview` key builder. With a `preview` provider the outputs of `create_presentation`
104
+ and `edit_presentation` gain `previews`, or `preview_error` when the pictures could not be made.
105
+ - **A slot with `"empty": "keep"` and a `box` now moves its shape.** Before, the `box` of such a slot was
106
+ ignored unless a deck gave the slot a value. `checkManifest` no longer reports that such a slot points at a
107
+ shape of another kind, or that it has no character budget.
108
+
109
+ ### Upgrading from 0.1.2
110
+
111
+ - A dependency range written for 0.1, such as `"slotdeck": "^0.1.2"`, does not take 0.2.0. Install it by
112
+ version: `npm install slotdeck@0.2.0`. The package itself has no new dependency; `fontkit` and the Pagus
113
+ packages are installed only by an app that wants measuring or previews.
114
+ - Nothing has to change. With a manifest made before and a template loaded without fonts, every result is the
115
+ same as in 0.1.2: character budgets, the same warnings, the same files. There is one exception: a manifest
116
+ that already had a slot with both `"empty": "keep"` and a `box` now shows that shape at the box. Remove the
117
+ `box` to keep the shape where the template drew it.
118
+ - To turn measuring on: `npm install fontkit`, build metrics with `fontkitMetrics`, and pass them to
119
+ `loadTemplate`. Code that builds a `Template` object by hand (`{ pkg, manifest }`) keeps working; add
120
+ `fonts` to it to measure.
121
+ - Once a template has fonts, a text longer than its `maxChars` that fits its box is no longer reported, and a
122
+ text within `maxChars` that does not fit is. Code that looks for the words `characters; the slot fits` in a
123
+ warning should also expect `lines`.
124
+ - Previews are off unless you pass a `preview` provider. To turn them on with Pagus:
125
+ `npm install @pagus-kit/core @pagus-kit/renderer` and `pagusPreviews(pagusCore, pagusRenderer)`.
126
+ - Design checks and reviews are off unless you call `checkDesign` or pass `review`. A review can make the
127
+ planner use more writing calls, since a draft within its limits may now be sent back.
128
+ - Budgets drafted without fonts are cautious. `measuredBudgets` usually suggests larger ones; setting them
129
+ lets the planning model write more per slide.
130
+ - To get mirrored layouts from your own template, run
131
+ `changeManifest(template, [{ op: 'derivePattern', pattern: '<id>', variant: 'mirror' }])` for each pattern with
132
+ a picture beside its text, render `sampleDeck` to look at the result, and store the new manifest.
133
+ `patternVariants(template, '<id>')` says which patterns allow it.
134
+ - A planner that is offered a pattern and its mirror image chooses between them by their descriptions. Give
135
+ each a description that says where the picture is.
136
+ - Rows and grids keep the size of their items unless the manifest opts in with `widen`, and decks made from
137
+ a manifest without it render byte for byte as before. After opting in, a slide with fewer items than drawn
138
+ looks different (its items are wider) and its text budgets are larger; text that fitted before still fits.
139
+ Render `sampleDeck(template, { items: 'min' })` to look at it before using the manifest.
140
+
6
141
  ## 0.1.2
7
142
 
8
143
  ### Added
@@ -38,7 +38,9 @@ const tools = presentationTools({
38
38
  layout: 'adaptive', // optional; default 'strict'
39
39
  fields: { organizationDepartment: 'Acme | Operations' }, // optional deck-level values every deck gets
40
40
  numberPatterns: 'from-brief', // optional
41
+ review, // optional DeckReview: a second look at every draft (design-checks.md)
41
42
  thumbnail: (patternId) => `/static/thumbnails/${patternId}.png`, // optional address of a picture of each layout
43
+ preview, // optional PreviewProvider: draws every presentation the tools make
42
44
  newId: (kind) => `${kind}_${crypto.randomUUID()}`, // optional; default is a short random id
43
45
  onProgress: (event, tool) => console.log(tool, event.stage), // optional
44
46
  });
@@ -141,6 +143,10 @@ Output:
141
143
 
142
144
  `file` is the store key of the rendered pptx. `warnings` is present only when there are any.
143
145
 
146
+ With the `preview` option set, the output also has `"previews"`: the store keys of the pictures of the slides,
147
+ such as `["preview/deck_851fbb9677/slide-01.svg", ...]`. If the pictures could not be made, it has
148
+ `"preview_error"` with the reason instead, and the presentation is created all the same.
149
+
144
150
  ### `edit_presentation`
145
151
 
146
152
  Arguments: `presentation_id` (string, required), `instruction` (string, required).
@@ -177,9 +183,10 @@ interface BlobStore {
177
183
  | `deck/<id>.json` | A deck: all text, and for each picture its id and description | 4 KB |
178
184
  | `asset/<id>` | One generated picture | tens of KB to 1 MB |
179
185
  | `pptx/<id>.pptx` | The rendered file | hundreds of KB and up |
186
+ | `preview/<id>/slide-01.svg` | A picture of one slide, when the tools have a `preview` provider. The extension follows the provider: `svg`, `png` | tens of KB each |
180
187
 
181
- `storeKeys.plan(id)`, `storeKeys.deck(id)`, `storeKeys.asset(id)` and `storeKeys.pptx(id)` build these keys, so
182
- your app can read what the tools saved:
188
+ `storeKeys.plan(id)`, `storeKeys.deck(id)`, `storeKeys.asset(id)`, `storeKeys.pptx(id)` and
189
+ `storeKeys.preview(id, slide, extension)` build these keys, so your app can read what the tools saved:
183
190
 
184
191
  ```ts
185
192
  const pptx = await store.get(storeKeys.pptx(presentationId)); // to serve a download later
@@ -219,9 +226,19 @@ const store: BlobStore = {
219
226
 
220
227
  ## Showing a plan visually
221
228
 
222
- A plan lists a layout per slide. To show layouts as pictures, render the template's slides to images once (with
223
- any tool you have), name each picture after the pattern made from that slide, and return its address from the
224
- `thumbnail` option. The address is passed through to the chat model's output and is not fetched by slotdeck.
229
+ A plan lists a layout per slide. To show layouts as pictures, make one picture for each pattern once, name it
230
+ after the pattern, and return its address from the `thumbnail` option. The address is passed through to the
231
+ chat model's output and is not fetched by slotdeck.
232
+
233
+ `patternPreviews(template, preview)` makes those pictures without PowerPoint, through a preview provider; see
234
+ [preview.md](preview.md). Pictures of the template's own slides from any other tool work as well.
235
+
236
+ ## Showing a presentation
237
+
238
+ Set the `preview` option to a `PreviewProvider` and every presentation the tools create or change is drawn.
239
+ The pictures go to the store under `preview/<presentation id>/`, their keys are in the tool's output, and the
240
+ `ToolResult` carries the pictures themselves in `previews` for your app to show. How to get a provider, and how
241
+ close its pictures are to PowerPoint, is in [preview.md](preview.md).
225
242
 
226
243
  ## A complete loop
227
244
 
@@ -223,3 +223,9 @@ const issues = validateDeck(manifest, deck, { layout: 'adaptive', assets: ['phot
223
223
 
224
224
  Errors make `renderDeck` throw. Warnings do not. Every message is listed in
225
225
  [troubleshooting.md](troubleshooting.md).
226
+
227
+ ## Another pattern for a slide
228
+
229
+ `slideAlternatives(manifest, deck, slideNumber)` lists the other patterns that can hold what a slide says, and
230
+ returns the slide written for each: cards as a list, a slide as its mirror image. It changes no words and
231
+ calls no model. See "Another layout for the same slide" in [layout-freedom.md](layout-freedom.md).
@@ -0,0 +1,216 @@
1
+ # Design checks and reviews
2
+
3
+ Validation (`validateDeck`) says whether a deck can be rendered and whether its text fits. This page is about
4
+ what comes after that: a deck that renders and fits can still look wrong. A body of two words in a box drawn
5
+ for a paragraph, one short card in a row of full ones, a title whose last word sits alone on a second line.
6
+
7
+ There are two tools:
8
+
9
+ - **`checkDesign`** finds such problems by measurement alone. No picture is made and no model is called.
10
+ - **A review** is a second look at each draft while a deck is being written. What it finds goes back to the
11
+ writing model in the same repair round as the limits the draft broke. A review can be the design checks, a
12
+ model that looks at pictures of the slides, or a function of your own. Nothing reviews a draft unless you ask.
13
+
14
+ Both need a template loaded with its fonts: see [text-fit.md](text-fit.md).
15
+
16
+ ## `checkDesign(template, deck, options?)`
17
+
18
+ ```ts
19
+ import { checkDesign } from 'slotdeck';
20
+
21
+ const problems = checkDesign(template, deck, { layout: 'adaptive' });
22
+ // [
23
+ // { severity: 'warning', slide: 1, slot: 'title', kind: 'orphan',
24
+ // message: 'the last of its 2 lines holds one word, "Share". Text of about 24 characters ends a line sooner; a few words more fill the line.' },
25
+ // ]
26
+ ```
27
+
28
+ It is synchronous and takes a fraction of a second for a deck. It throws when the template has no fonts.
29
+
30
+ ### What it finds
31
+
32
+ | `kind` | Problem | Message |
33
+ | --- | --- | --- |
34
+ | `overflow` | Text runs over its box. The same finding, with the same words, as the warning of `validateDeck` with fonts. | `text takes 7 lines at 18 pt; the box holds 5 (2 lines too many). Text of about 290 characters fits.` |
35
+ | `empty` | Text takes a third or less of the lines the template's own text takes in that box, so the box looks empty. | `text takes 1 line where the template's own text takes 5, so its box will look empty. Text of about 45 characters fills it as designed.` |
36
+ | `uneven` | In a row of cards, one card's text takes a third or less of the lines of its fullest neighbour. | `text takes 1 line where "card1Text" beside it takes 5, so the row will look uneven. Text of about 66 characters or more evens it out.` |
37
+ | `orphan` | A heading ends with one word on a line of its own. | `the last of its 2 lines holds one word, "Share". Text of about 24 characters ends a line sooner; a few words more fill the line.` |
38
+
39
+ Every message says what to do about it, with a length in characters. That is what the writing model can act
40
+ on, so a message can be sent to it as it is.
41
+
42
+ ### The result
43
+
44
+ ```ts
45
+ interface DesignIssue {
46
+ severity: 'warning';
47
+ slide: number; // 1-based
48
+ slot: string; // the slot name as the deck uses it
49
+ kind: 'overflow' | 'empty' | 'uneven' | 'orphan';
50
+ message: string;
51
+ }
52
+ ```
53
+
54
+ A `DesignIssue` is an `Issue` with a `kind`, so the list can be shown or stored together with the warnings of
55
+ `validateDeck` and `renderDeck`.
56
+
57
+ ### Options
58
+
59
+ | Option | Default | Meaning |
60
+ | --- | --- | --- |
61
+ | `layout` | `'strict'` | As for `renderDeck`: with `'adaptive'`, optional slots may be left out. |
62
+ | `kinds` | all four | Which problems to look for, e.g. `['empty', 'orphan']`. |
63
+ | `emptyBelow` | `1/3` | A text is `empty` when it takes this share or less of the lines the template's own text takes; a card is `uneven` at this share or less of its fullest neighbour. |
64
+ | `headingSize` | `20` | The font size in points from which a single paragraph counts as a heading, for `orphan`. |
65
+
66
+ ### How each check decides
67
+
68
+ **`overflow`.** The measured text needs more height than its box gives. If the manifest has `shrinkText`, a
69
+ text that fits once it is set smaller is not reported.
70
+
71
+ **`empty`.** The reference is the template itself: the lines its own sample text takes in the slot. A slot is
72
+ looked at only when that is three lines or more, so a title or a one-line caption is never "empty". A slot that
73
+ the manifest resized (`box`, `fontSize`: a derived pattern) has no sample to compare with; its box is
74
+ then taken to be meant half full. A `box` that only moves the shape, as in a mirrored pattern, keeps the
75
+ comparison. In a row that widens its items, the reference is the item as drawn: a short text in a wide card
76
+ takes fewer lines, so it is reported sooner, with the length that fills the wider card. A slot fed by a deck field is not checked, because that text is the app's.
77
+
78
+ **`uneven`.** The slots of one kind across a row are compared: every card's text with every other card's text,
79
+ every heading with every heading. Rows come from the pattern's `repeat` or its `sequences`. A row is looked at
80
+ only when its fullest item takes three lines or more. Items whose text overflows are left out of the
81
+ comparison.
82
+
83
+ **`orphan`.** A text of one paragraph, set at `headingSize` or larger, on two lines or more, whose last line
84
+ is a single word no wider than two fifths of the widest line. An address (`name@example.org`, a web address)
85
+ on its own line is not reported: it is meant to stand there.
86
+
87
+ ### What it does not see
88
+
89
+ - Tables, charts and pictures. Only text slots are measured.
90
+ - Slots set in a typeface that was not supplied. They are passed over (see `missingFonts` in
91
+ [text-fit.md](text-fit.md)).
92
+ - Anything that needs a picture to judge: colours, contrast, a photo that does not suit the text.
93
+ - Whether a line break looks good in the middle of a heading. Only the last line is looked at.
94
+
95
+ On this project's hand-written example deck the checks report two things, both real: the cover title ends with
96
+ "Share" on a second line, and the title of slide 4 with "trip". PowerPoint's own picture of the deck shows
97
+ both.
98
+
99
+ ## Reviews: a second look while a deck is written
100
+
101
+ The planner writes a draft, validates it, and sends a draft that breaks the template's limits back to the
102
+ model (see "How planning works" in [generating.md](generating.md)). A review adds its own findings to that
103
+ round.
104
+
105
+ ```ts
106
+ import { designReview, planDeck } from 'slotdeck';
107
+
108
+ const { deck, warnings } = await planDeck(template, brief, {
109
+ llm,
110
+ review: designReview(template), // empty boxes, uneven cards, orphan words
111
+ });
112
+ ```
113
+
114
+ The `review` option is accepted wherever a deck is written: `planDeck`, `writeDeck`, `generateDeck`,
115
+ `planEdit`, `editDeck`, and `presentationTools`.
116
+
117
+ How it works:
118
+
119
+ 1. After each draft, validation runs as always. If the draft has no errors, the review is called with it.
120
+ 2. What validation and the review found on the slides just written goes back to the model, each as one line:
121
+ `- s3.title: the last of its 2 lines holds one word, ...`.
122
+ 3. This repeats up to `maxRepairs` times (2 by default). A review therefore runs up to three times for a deck.
123
+ 4. Whatever is still found after the last round is returned in `warnings`, next to the validation warnings.
124
+
125
+ A review can make the planner use more model calls: a draft that was within its limits may now be sent back.
126
+ With `designReview` the review itself costs nothing; each repair it causes is one writing call.
127
+
128
+ ### `designReview(template, options?)`
129
+
130
+ The code-only checks as a review. It reports `empty`, `uneven` and `orphan`; `overflow` is left to validation,
131
+ which reports it anyway. `options` are those of `checkDesign`.
132
+
133
+ ### A review of your own
134
+
135
+ A review is a function from a draft deck to problems:
136
+
137
+ ```ts
138
+ import type { DeckReview, Issue } from 'slotdeck';
139
+
140
+ const review: DeckReview = async (deck, { signal }) => {
141
+ const problems: Issue[] = [];
142
+ deck.slides.forEach((slide, index) => {
143
+ if (typeof slide.slots.title === 'string' && slide.slots.title.endsWith(':')) {
144
+ problems.push({ severity: 'warning', slide: index + 1, slot: 'title', message: 'a title must not end with a colon' });
145
+ }
146
+ });
147
+ return problems;
148
+ };
149
+ ```
150
+
151
+ Each problem is an `Issue`: `severity`, a 1-based `slide`, a `slot` when it is about one, and a `message`
152
+ written for the model. A problem without a `slide`, or on a slide the model was not asked to write, is not
153
+ sent back; it is returned in `warnings` all the same.
154
+
155
+ ### `visionReview(options)`: a model that looks at the slides
156
+
157
+ ```ts
158
+ import { planDeck, visionReview } from 'slotdeck';
159
+
160
+ const review = visionReview({
161
+ template,
162
+ llm: visionModel, // an LlmProvider whose model can look at pictures
163
+ preview, // a PreviewProvider that gives PNG or JPEG
164
+ icons, // optional: the app's icons and supplied pictures, drawn as they are
165
+ assets,
166
+ });
167
+ const { deck, warnings } = await planDeck(template, brief, { llm, review });
168
+ ```
169
+
170
+ For each draft it:
171
+
172
+ 1. renders the deck, with a plain grey rectangle for every picture that does not exist yet (photos are
173
+ generated after planning);
174
+ 2. asks the preview provider for a picture of each slide;
175
+ 3. shows the model the pictures, six slides to a request, with the text of each slide's slots, and asks only
176
+ for what can be seen and fixed by changing text: text cut off or lying over something, text too small to
177
+ read, a box or slide left mostly empty, a heading that ends in a lone word;
178
+ 4. returns what the model reports, each as `seen in the picture of the slide: ...`, with the slot when the
179
+ model named one that the slide has.
180
+
181
+ | Option | Default | Meaning |
182
+ | --- | --- | --- |
183
+ | `template` | required | The template the deck is made from. |
184
+ | `llm` | required | A provider whose model accepts pictures (`JsonRequest.images`). It can be another model than the one that writes. |
185
+ | `preview` | required | Draws the draft. It must give bitmap pictures: PNG, JPEG, WebP or GIF. |
186
+ | `icons`, `assets` | none | Pictures the deck may use, drawn as they are instead of as rectangles. |
187
+ | `layout` | `'adaptive'` | As for `renderDeck`. |
188
+ | `slidesPerRequest` | `6` | How many slides are shown to the model in one request. |
189
+
190
+ Each request is capped at 300 output tokens plus 200 for each slide in it. A draft that cannot be rendered is
191
+ not looked at: its errors are validation's to report.
192
+
193
+ **Read this before turning it on.**
194
+
195
+ - **It is only as good as the pictures.** A model that is shown a preview which differs from PowerPoint will
196
+ report problems that are not there. The Pagus adapter that comes with slotdeck gives SVG, which a model
197
+ cannot read, and `visionReview` refuses it with an error. Use a provider whose pictures can be trusted, such
198
+ as PowerPoint's own export on a machine that has it. See "How close the pictures are" in
199
+ [preview.md](preview.md).
200
+ - **It has not been run against a live model, or against faithful pictures.** It was built and tested with a
201
+ scripted model and made-up pictures: what is sent, how the answer is read, how it takes part in the repair
202
+ round. How well a real model judges real slides with this prompt is not known. Try it on your own decks
203
+ before relying on it.
204
+ - **It costs model calls with pictures:** one request for every six slides, for every draft, so up to three
205
+ times a deck with the default `maxRepairs`.
206
+ - The code-only checks already cover overflow, empty boxes, uneven cards and orphan words, exactly and at no
207
+ cost. A vision review is for what measurement cannot see.
208
+
209
+ ### Reviews in the chat tools
210
+
211
+ ```ts
212
+ const tools = presentationTools({ template, llm, store, review: designReview(template) });
213
+ ```
214
+
215
+ The review then runs for every deck `create_presentation` writes and for the slides `edit_presentation`
216
+ rewrites. What it leaves unfixed is in the tool's `warnings`.
@@ -50,6 +50,7 @@ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
50
50
  | `patterns` | string[] | all | Restrict the deck to these pattern ids. |
51
51
  | `numberPatterns` | `"from-brief"`, `"always"`, `"never"` | `from-brief` | When patterns marked `figures` may be used. `from-brief` offers them only if the brief contains digits. |
52
52
  | `maxRepairs` | number | 2 | How many times a draft that breaks the template's limits is sent back to the model. |
53
+ | `review` | `DeckReview` | none | A second look at every draft: design checks, a model that looks at pictures of the slides, or a function of your own. What it finds goes back to the model in the repair rounds. See [design-checks.md](design-checks.md). |
53
54
  | `cache` | `Map<string, Uint8Array>` | none | Generated pictures by asset id. Pass the same map on later calls and pictures whose description did not change are reused. |
54
55
  | `imageConcurrency` | number | 3 | How many pictures to generate at once. |
55
56
  | `onProgress` | function | none | Called with `{ stage: 'outline' }`, `{ stage: 'write', slides }`, `{ stage: 'repair', attempt, issues }`, `{ stage: 'images', done, total }`, `{ stage: 'render' }`. |
@@ -88,7 +89,9 @@ const { pptx, deck, warnings } = await generateDeck(
88
89
  2. **Write** (one model call). The model fills a JSON schema built for exactly that sequence of slides, so it
89
90
  cannot invent patterns or slots.
90
91
  3. **Repair** (up to `maxRepairs` more calls). The draft is validated against the manifest. Errors and
91
- over-budget text go back to the model with the list of problems.
92
+ over-budget text go back to the model with the list of problems. For a template loaded with fonts the
93
+ text is measured, and the problems are stated in lines ([text-fit.md](text-fit.md)). With a `review`, what
94
+ the review finds on a draft goes back in the same list ([design-checks.md](design-checks.md)).
92
95
 
93
96
  Which patterns are offered depends on what you supplied:
94
97