slotdeck 0.1.1 → 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 (109) hide show
  1. package/README.md +94 -8
  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 +44 -6
  10. package/dist/deck/validate.js.map +1 -1
  11. package/dist/index.d.ts +23 -1
  12. package/dist/index.js +14 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/llm/gemini.d.ts +4 -0
  15. package/dist/llm/gemini.js +43 -7
  16. package/dist/llm/gemini.js.map +1 -1
  17. package/dist/llm/provider.d.ts +12 -0
  18. package/dist/node.d.ts +7 -0
  19. package/dist/node.js +29 -0
  20. package/dist/node.js.map +1 -1
  21. package/dist/plan/catalog.d.ts +11 -1
  22. package/dist/plan/catalog.js +23 -8
  23. package/dist/plan/catalog.js.map +1 -1
  24. package/dist/plan/edit.js +14 -6
  25. package/dist/plan/edit.js.map +1 -1
  26. package/dist/plan/plan.d.ts +26 -2
  27. package/dist/plan/plan.js +58 -17
  28. package/dist/plan/plan.js.map +1 -1
  29. package/dist/plan/review.d.ts +43 -0
  30. package/dist/plan/review.js +141 -0
  31. package/dist/plan/review.js.map +1 -0
  32. package/dist/plan/write.d.ts +13 -1
  33. package/dist/plan/write.js +40 -7
  34. package/dist/plan/write.js.map +1 -1
  35. package/dist/preview/pagus.d.ts +30 -0
  36. package/dist/preview/pagus.js +45 -0
  37. package/dist/preview/pagus.js.map +1 -0
  38. package/dist/preview/patterns.d.ts +27 -0
  39. package/dist/preview/patterns.js +35 -0
  40. package/dist/preview/patterns.js.map +1 -0
  41. package/dist/preview/provider.d.ts +30 -0
  42. package/dist/preview/provider.js +11 -0
  43. package/dist/preview/provider.js.map +1 -0
  44. package/dist/render/render.d.ts +18 -1
  45. package/dist/render/render.js +32 -12
  46. package/dist/render/render.js.map +1 -1
  47. package/dist/render/repeat.d.ts +17 -2
  48. package/dist/render/repeat.js +150 -4
  49. package/dist/render/repeat.js.map +1 -1
  50. package/dist/render/text.d.ts +6 -0
  51. package/dist/render/text.js +24 -0
  52. package/dist/render/text.js.map +1 -1
  53. package/dist/template/budgets.d.ts +33 -0
  54. package/dist/template/budgets.js +38 -0
  55. package/dist/template/budgets.js.map +1 -0
  56. package/dist/template/change.d.ts +19 -1
  57. package/dist/template/change.js +45 -3
  58. package/dist/template/change.js.map +1 -1
  59. package/dist/template/check.d.ts +3 -0
  60. package/dist/template/check.js +53 -3
  61. package/dist/template/check.js.map +1 -1
  62. package/dist/template/derive.d.ts +54 -0
  63. package/dist/template/derive.js +402 -0
  64. package/dist/template/derive.js.map +1 -0
  65. package/dist/template/draft.d.ts +7 -0
  66. package/dist/template/draft.js +17 -1
  67. package/dist/template/draft.js.map +1 -1
  68. package/dist/template/instruct.js +15 -7
  69. package/dist/template/instruct.js.map +1 -1
  70. package/dist/template/manifest.d.ts +38 -0
  71. package/dist/template/manifest.js +43 -1
  72. package/dist/template/manifest.js.map +1 -1
  73. package/dist/template/sample.d.ts +17 -0
  74. package/dist/template/sample.js +31 -5
  75. package/dist/template/sample.js.map +1 -1
  76. package/dist/text/fontkit.d.ts +18 -0
  77. package/dist/text/fontkit.js +91 -0
  78. package/dist/text/fontkit.js.map +1 -0
  79. package/dist/text/fonts.d.ts +45 -0
  80. package/dist/text/fonts.js +59 -0
  81. package/dist/text/fonts.js.map +1 -0
  82. package/dist/text/layout.d.ts +12 -0
  83. package/dist/text/layout.js +104 -0
  84. package/dist/text/layout.js.map +1 -0
  85. package/dist/text/measure.d.ts +85 -0
  86. package/dist/text/measure.js +369 -0
  87. package/dist/text/measure.js.map +1 -0
  88. package/dist/text/styles.d.ts +123 -0
  89. package/dist/text/styles.js +417 -0
  90. package/dist/text/styles.js.map +1 -0
  91. package/dist/tools.d.ts +12 -0
  92. package/dist/tools.js +38 -6
  93. package/dist/tools.js.map +1 -1
  94. package/docs/README.md +16 -5
  95. package/docs/api-reference.md +291 -9
  96. package/docs/changelog.md +242 -0
  97. package/docs/chat-tools.md +26 -5
  98. package/docs/deck-format.md +6 -0
  99. package/docs/design-checks.md +216 -0
  100. package/docs/generating.md +43 -4
  101. package/docs/layout-freedom.md +278 -3
  102. package/docs/manifest-format.md +20 -3
  103. package/docs/preview.md +217 -0
  104. package/docs/providers.md +49 -4
  105. package/docs/rendering.md +8 -0
  106. package/docs/template-studio.md +121 -12
  107. package/docs/text-fit.md +456 -0
  108. package/docs/troubleshooting.md +21 -2
  109. package/package.json +7 -1
@@ -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
  });
@@ -86,6 +88,7 @@ Arguments:
86
88
  | `language` | string | no | BCP 47 tag, such as `he` or `en`. Defaults to the template's language. |
87
89
  | `image_density` | `none`, `low`, `medium`, `high` | no | How many slides carry a photo. Default `medium`. Forced to `none` when no photos can be made. |
88
90
  | `text_size` | `normal`, `large`, `x-large` | no | Size of body text. Default `normal`. |
91
+ | `text_only` | boolean | no | `true` when the person asks for a presentation of text only. Every slide is then plain text: a title with paragraphs or a list, with no pictures, icons, cards, figures, tables or charts. Only the template's plain text layouts are used. |
89
92
  | `instructions` | string | no | Audience, tone and anything else to respect. |
90
93
 
91
94
  Output:
@@ -140,6 +143,10 @@ Output:
140
143
 
141
144
  `file` is the store key of the rendered pptx. `warnings` is present only when there are any.
142
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
+
143
150
  ### `edit_presentation`
144
151
 
145
152
  Arguments: `presentation_id` (string, required), `instruction` (string, required).
@@ -176,9 +183,10 @@ interface BlobStore {
176
183
  | `deck/<id>.json` | A deck: all text, and for each picture its id and description | 4 KB |
177
184
  | `asset/<id>` | One generated picture | tens of KB to 1 MB |
178
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 |
179
187
 
180
- `storeKeys.plan(id)`, `storeKeys.deck(id)`, `storeKeys.asset(id)` and `storeKeys.pptx(id)` build these keys, so
181
- 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:
182
190
 
183
191
  ```ts
184
192
  const pptx = await store.get(storeKeys.pptx(presentationId)); // to serve a download later
@@ -218,9 +226,19 @@ const store: BlobStore = {
218
226
 
219
227
  ## Showing a plan visually
220
228
 
221
- A plan lists a layout per slide. To show layouts as pictures, render the template's slides to images once (with
222
- any tool you have), name each picture after the pattern made from that slide, and return its address from the
223
- `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).
224
242
 
225
243
  ## A complete loop
226
244
 
@@ -247,6 +265,9 @@ async function runTurn(messages) {
247
265
  The tool descriptions already tell the model the order to work in. It helps to add, in your own system prompt:
248
266
 
249
267
  - Put every fact, figure and name the person gave into `topic`. Numbers that are not in `topic` will not be used.
268
+ - Pass wishes about the kind of slides on, in the person's words ("no tables", "only cards"), in `topic` or
269
+ `instructions`. For a presentation of text only, set `text_only: true`: "no pictures" alone
270
+ (`image_density: "none"`) still allows cards, tables and charts.
250
271
  - Show the plan and wait for approval before calling `create_presentation`.
251
272
  - For a change to a finished presentation, call `edit_presentation` with the person's words; do not plan again
252
273
  from scratch.
@@ -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`.
@@ -36,6 +36,7 @@ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
36
36
  | `instructions` | string | none | Anything else to respect: audience, tone, points that must be covered. |
37
37
  | `fields` | object of strings | none | Deck-level values the template needs (see "Deck fields" in [deck-format.md](deck-format.md)). A pattern that needs a field that is not given is not offered. |
38
38
  | `textSize` | `"normal"`, `"large"`, `"x-large"` | `normal` | Size of body text. Larger text gets smaller budgets, so less is written. |
39
+ | `textOnly` | boolean | `false` | Plan every slide as plain text. Only the template's plain text patterns are offered. See "Asking for a kind of slide" below. |
39
40
 
40
41
  ### Options (`GenerateOptions`)
41
42
 
@@ -49,6 +50,7 @@ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
49
50
  | `patterns` | string[] | all | Restrict the deck to these pattern ids. |
50
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. |
51
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). |
52
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. |
53
55
  | `imageConcurrency` | number | 3 | How many pictures to generate at once. |
54
56
  | `onProgress` | function | none | Called with `{ stage: 'outline' }`, `{ stage: 'write', slides }`, `{ stage: 'repair', attempt, issues }`, `{ stage: 'images', done, total }`, `{ stage: 'render' }`. |
@@ -87,7 +89,9 @@ const { pptx, deck, warnings } = await generateDeck(
87
89
  2. **Write** (one model call). The model fills a JSON schema built for exactly that sequence of slides, so it
88
90
  cannot invent patterns or slots.
89
91
  3. **Repair** (up to `maxRepairs` more calls). The draft is validated against the manifest. Errors and
90
- 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)).
91
95
 
92
96
  Which patterns are offered depends on what you supplied:
93
97
 
@@ -105,6 +109,37 @@ model.
105
109
 
106
110
  If nothing can be offered, `PlanError` is thrown with the reasons.
107
111
 
112
+ ### Asking for a kind of slide
113
+
114
+ By default the planner varies the patterns across a deck. A wish about the kind of slides overrides that, and
115
+ there are two ways to state one.
116
+
117
+ **In words.** A wish in `prompt` or `instructions`, such as "text only", "no tables" or "only cards", decides
118
+ which patterns may be used and comes before variety. The planner is told so, and the pattern list it reads marks
119
+ each plain text pattern with the words "Plain text.".
120
+
121
+ **As a flag.** `textOnly: true` is the reliable way to get a deck of text and nothing else. The planner is then
122
+ offered only the plain text patterns, so no other kind can be chosen, and it is told that using the same pattern
123
+ for several slides in a row is expected.
124
+
125
+ ```ts
126
+ await generateDeck(template, { prompt: 'Our move to hybrid work: why, the rules, what changed', textOnly: true }, { llm });
127
+ // for example: cover, text-only, text-only, text-only, text-only, closing
128
+ ```
129
+
130
+ A pattern counts as **plain text** when a slide made from it holds running text and nothing else: all its
131
+ slots are text (a picture that the pattern always drops does not count), it has no row or grid of items, and it
132
+ is not marked `figures`. A cover, a closing slide, a quotation and a title-with-paragraphs slide are plain text;
133
+ cards, tables, charts and anything with a picture or icon are not.
134
+
135
+ Two things to know:
136
+
137
+ - "No pictures" is a different wish. `imageDensity: 'none'` removes photos but still allows cards, tables and
138
+ charts.
139
+ - A text-only deck is only as varied as the template's plain text patterns. A template without one cannot make
140
+ such a deck: `PlanError: Template "x" has no plain text pattern, so a text-only deck cannot be planned from it`.
141
+ Add one as a derived pattern (see [layout-freedom.md](layout-freedom.md)).
142
+
108
143
  ### Why numbers are guarded
109
144
 
110
145
  A model given a slide built around a statistic and no data tends to invent one. So patterns marked `figures` are
@@ -210,9 +245,13 @@ Warnings (text a little over a budget, for instance) do not throw. They are retu
210
245
 
211
246
  ## Cost and time
212
247
 
213
- For one deck: two text calls, more if repairs are needed, and one image call per generated photo. On the demo
214
- template with `gemini-3.8-flash`, a nine-slide deck took about 50 seconds including four pictures. An edit that
215
- rewrites one slide took about 15 seconds.
248
+ For one deck: two text calls, more if repairs are needed, and one image call per generated photo. Measured on
249
+ the demo template with `gemini-3.8-flash`: a plan takes about 5 seconds, an eight-slide deck about 30 seconds
250
+ from brief to file (with drawn stand-in pictures), and an edit that rewrites one slide about 8 seconds.
251
+
252
+ Planning calls (the outline, a change to a plan, the first step of an edit) ask the model for light reasoning,
253
+ because they choose and arrange rather than write. This made them several times faster and removed the runaway
254
+ answers described in [providers.md](providers.md). Writing the slides keeps the model's default.
216
255
 
217
256
  To keep cost down: reuse `cache` across calls, set `imageDensity: 'low'` or `'none'`, and use `planOutline`
218
257
  first so a person can correct the plan before content and pictures are produced.