slotdeck 0.1.0 → 0.1.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.
@@ -0,0 +1,218 @@
1
+ # Generating and editing decks with a model
2
+
3
+ The planner turns a brief into deck JSON by asking a language model you provide. The model never produces a
4
+ file and never positions anything: it chooses patterns from the manifest and writes text for their slots, inside
5
+ a JSON schema built from the manifest.
6
+
7
+ ```ts
8
+ import { generateDeck, editDeck, planDeck, planOutline, editPlan, writeDeck, planEdit, resolveAssets, PlanError, DeckValidationError } from 'slotdeck';
9
+ import type { Brief, PlanOptions, GenerateOptions, IconDef, AssetDef } from 'slotdeck';
10
+ ```
11
+
12
+ All functions on this page need an `LlmProvider` (see [providers.md](providers.md)).
13
+
14
+ ## `generateDeck(template, brief, options)`: brief in, pptx out
15
+
16
+ ```ts
17
+ const result = await generateDeck(template, brief, options);
18
+ // result.pptx Uint8Array the file
19
+ // result.deck DeckSpec store this: it is what gets edited and rendered again
20
+ // result.assets Record<string, Uint8Array> every picture the deck uses, by asset id
21
+ // result.warnings Issue[] limits the final draft still exceeds
22
+ // result.outline OutlineEntry[] [{ pattern, purpose, items? }] for each slide
23
+ ```
24
+
25
+ It runs `planDeck`, then `resolveAssets`, then `renderDeck`.
26
+
27
+ ### `Brief`
28
+
29
+ | Field | Type | Default | Meaning |
30
+ | --- | --- | --- | --- |
31
+ | `prompt` | string | required | What the deck is about, in the person's own words. Include every fact, figure and name that should appear: the model is told to use only what is stated. |
32
+ | `slideCount` | number, or `{ min, max }` | 6 to 10 | A fixed number of slides, or a range. |
33
+ | `language` | string | the template's | BCP 47 tag of the language to write in. |
34
+ | `direction` | `"ltr"` or `"rtl"` | the language's | Reading direction. |
35
+ | `imageDensity` | `"none"`, `"low"`, `"medium"`, `"high"` | `medium` | How many slides carry a photo: none, at most a quarter, about half, or wherever it suits. Forced to `none` by `generateDeck` when no image source and no supplied photos were given. |
36
+ | `instructions` | string | none | Anything else to respect: audience, tone, points that must be covered. |
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
+ | `textSize` | `"normal"`, `"large"`, `"x-large"` | `normal` | Size of body text. Larger text gets smaller budgets, so less is written. |
39
+
40
+ ### Options (`GenerateOptions`)
41
+
42
+ | Option | Type | Default | Meaning |
43
+ | --- | --- | --- | --- |
44
+ | `llm` | `LlmProvider` | required | The text model. |
45
+ | `images` | `ImageProvider` | none | Makes photos from descriptions. Without it, decks are planned with no generated photos. |
46
+ | `icons` | `IconDef[]` | none | Your icon set: `{ id, description, data }`. Patterns with icon slots are offered only when this is given. The model picks icons by `description`, so describe what each shows and what it stands for ("a clock: time, schedule"). |
47
+ | `assets` | `AssetDef[]` | none | Pictures you already have: `{ id, kind: 'photo' | 'graphic', description, data }`. The model decides where each fits from its description. |
48
+ | `layout` | `"strict"` or `"adaptive"` | `strict` | Whether optional slots may be left out. |
49
+ | `patterns` | string[] | all | Restrict the deck to these pattern ids. |
50
+ | `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
+ | `maxRepairs` | number | 2 | How many times a draft that breaks the template's limits is sent back to the model. |
52
+ | `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
+ | `imageConcurrency` | number | 3 | How many pictures to generate at once. |
54
+ | `onProgress` | function | none | Called with `{ stage: 'outline' }`, `{ stage: 'write', slides }`, `{ stage: 'repair', attempt, issues }`, `{ stage: 'images', done, total }`, `{ stage: 'render' }`. |
55
+ | `signal` | `AbortSignal` | none | Cancels the work. |
56
+
57
+ ### Example
58
+
59
+ ```ts
60
+ const { pptx, deck, warnings } = await generateDeck(
61
+ template,
62
+ {
63
+ prompt: 'A deck for new adopters: why adopt a rescue dog, the first month at home, what it costs. We rehomed 286 dogs in 2023, 351 in 2024 and 412 in 2025.',
64
+ language: 'en',
65
+ slideCount: 9,
66
+ imageDensity: 'medium',
67
+ fields: { organizationDepartment: 'Harbor Street Shelter | Adoption team' },
68
+ },
69
+ {
70
+ llm,
71
+ images,
72
+ icons: [
73
+ { id: 'clock', description: 'a clock: time, schedule, duration', data: clockPng },
74
+ { id: 'heart', description: 'a heart: care, health, loyalty', data: heartPng },
75
+ ],
76
+ assets: [{ id: 'flow', kind: 'graphic', description: 'diagram of the adoption process in four steps', data: flowPng }],
77
+ layout: 'adaptive',
78
+ onProgress: (event) => console.log(event.stage),
79
+ },
80
+ );
81
+ ```
82
+
83
+ ## How planning works
84
+
85
+ 1. **Outline** (one model call). The model sees every usable pattern with its description and slot limits, and
86
+ answers with one pattern per slide and the point each slide makes.
87
+ 2. **Write** (one model call). The model fills a JSON schema built for exactly that sequence of slides, so it
88
+ cannot invent patterns or slots.
89
+ 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.
91
+
92
+ Which patterns are offered depends on what you supplied:
93
+
94
+ | A pattern with | Is offered only when |
95
+ | --- | --- |
96
+ | a required photo slot | `imageDensity` is not `none`. `generateDeck` and the chat tools set it to `none` when there is no image source and no supplied photo. If you call `planDeck` yourself without a way to make photos, set `imageDensity: 'none'`. |
97
+ | a required icon slot | `icons` is given |
98
+ | a required graphic slot | `assets` has a picture of kind `graphic` |
99
+ | a required deck field | `brief.fields` has it |
100
+ | `figures: true` | the brief contains digits (or `numberPatterns: 'always'`) |
101
+
102
+ "Required" follows the layout: in `strict` layout an optional slot (`empty: "remove"`) counts as required,
103
+ because nothing may be removed from a slide. Slots with `empty` of `keep` or `drop` are never shown to the
104
+ model.
105
+
106
+ If nothing can be offered, `PlanError` is thrown with the reasons.
107
+
108
+ ### Why numbers are guarded
109
+
110
+ A model given a slide built around a statistic and no data tends to invent one. So patterns marked `figures` are
111
+ held back unless the brief itself contains figures, and chart values that the brief does not state come back as
112
+ warnings such as `chart values not stated in the brief: 89`. Values worked out from the brief, such as a sum, are
113
+ flagged too; treat the warning as a prompt to check.
114
+
115
+ Contact details and names are not guarded. A model asked for a closing slide may invent an email address. State
116
+ such details in the brief, or review the deck.
117
+
118
+ ## The steps on their own
119
+
120
+ Use these when a person should see and approve a plan before the content is written.
121
+
122
+ ```ts
123
+ // 1. Decide the slides. One call, quick, small result: the thing to show a person.
124
+ const plan = await planOutline(template, brief, { llm, icons, layout: 'adaptive' });
125
+ // plan = { version: 1, title, brief, slides: [{ pattern, purpose, items? }] }
126
+
127
+ // 2. Change the plan by instruction. One call. Returns a new plan and one sentence on what changed.
128
+ const { plan: revised, summary } = await editPlan(template, plan, 'Add a slide about pricing before the end', { llm, icons, layout: 'adaptive' });
129
+
130
+ // 3. Write the content. One call plus repairs.
131
+ const { deck, warnings, outline } = await writeDeck(template, revised, { llm, icons, layout: 'adaptive' });
132
+
133
+ // 4. Pictures and file.
134
+ const assets = await resolveAssets(template, deck, { images, icons, assets: supplied });
135
+ const { pptx } = renderDeck(template, deck, { assets, layout: 'adaptive' });
136
+ ```
137
+
138
+ `planDeck(template, brief, options)` is steps 1 and 3 in one call and returns `{ deck, warnings, outline }`.
139
+
140
+ Pass the same `icons`, `assets` and `layout` to every step: the plan was made on the assumption that they are
141
+ available.
142
+
143
+ ### `resolveAssets(template, deck, options?)`
144
+
145
+ Collects the bytes of every picture a deck refers to: supplied icons and assets as they are, described pictures
146
+ from `cache` or from `images`. Returns a map from asset id to bytes, ready for `renderDeck`.
147
+
148
+ | Option | Meaning |
149
+ | --- | --- |
150
+ | `images` | The `ImageProvider` that makes described pictures. |
151
+ | `icons`, `assets` | The same sets the deck was planned with. |
152
+ | `cache` | A `Map` of pictures already made. New ones are added to it. |
153
+ | `concurrency` | How many to generate at once (default 3). |
154
+ | `onProgress`, `signal` | As above. |
155
+
156
+ The image provider is asked for a picture with the aspect ratio of the frame it goes into.
157
+
158
+ ## Editing a deck
159
+
160
+ ```ts
161
+ const edited = await editDeck(template, deck, 'Shorten slide 3 to two bullets and translate the deck to Hebrew', {
162
+ llm,
163
+ images,
164
+ icons,
165
+ assets,
166
+ cache, // pictures of the earlier version; see extractAssets in rendering.md
167
+ layout: 'adaptive',
168
+ });
169
+ // edited.deck the new deck
170
+ // edited.pptx the new file
171
+ // edited.assets its pictures
172
+ // edited.summary one sentence from the model on what it changed
173
+ // edited.rewritten numbers of the slides in the new deck that were written or rewritten
174
+ // edited.warnings
175
+ ```
176
+
177
+ An edit is two model calls. The first decides what the deck looks like after the change: which slides stay,
178
+ move, go, get rewritten or are new. The second writes only the slides that change. Reordering and deleting need
179
+ no second call.
180
+
181
+ - Slides the instruction does not touch are copied by code, so their text cannot drift.
182
+ - A rewritten slide keeps its pictures unless the instruction is about them.
183
+ - A slide given a different pattern takes its photo along.
184
+ - A change of language rewrites every slide, sets the reading direction and translates deck-level fields.
185
+ - "Make the text larger" sets `textScale` and rewrites only the slides whose text no longer fits.
186
+ - "Use three cards instead of four" changes `items` on that slide.
187
+
188
+ `planEdit(template, deck, instruction, options)` does the same without pictures or rendering and returns
189
+ `{ deck, summary, rewritten, warnings }`.
190
+
191
+ Extra options for edits (`EditOptions`):
192
+
193
+ | Option | Meaning |
194
+ | --- | --- |
195
+ | `fields` | Replaces the deck-level values, for example a translated header text. Defaults to the deck's own. |
196
+ | `direction` | Reading direction after the edit. Defaults to the direction of the deck's language. |
197
+ | `imageDensity` | Whether slides added by the edit may use photo patterns. Default `medium`. |
198
+
199
+ Changes made by hand in PowerPoint are not read back. The deck JSON is the source of truth.
200
+
201
+ ## Errors
202
+
203
+ | Error | When | What to do |
204
+ | --- | --- | --- |
205
+ | `PlanError` | No pattern can be used with what was supplied; or the model's outline or edit plan is unusable; or the plan uses patterns that cannot be filled | Read the message: it names the patterns and reasons. Supply the missing icons, fields or images, or retry. |
206
+ | `DeckValidationError` | After all repairs the draft still has error-level issues | `error.issues` lists them. Retry, raise `maxRepairs`, or simplify the brief. |
207
+ | Anything your provider throws | The model call failed | Handled by you: slotdeck does not retry model calls. |
208
+
209
+ Warnings (text a little over a budget, for instance) do not throw. They are returned with the result.
210
+
211
+ ## Cost and time
212
+
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.
216
+
217
+ To keep cost down: reuse `cache` across calls, set `imageDensity: 'low'` or `'none'`, and use `planOutline`
218
+ first so a person can correct the plan before content and pictures are produced.
@@ -0,0 +1,139 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install slotdeck
7
+ ```
8
+
9
+ - Node 22.18 or later.
10
+ - ESM only. Your `package.json` needs `"type": "module"`, or your files need the `.mjs` extension (or you use
11
+ TypeScript with ESM output).
12
+ - Two runtime dependencies come with it: `fflate` (zip) and `@xmldom/xmldom` (XML). There is no native code
13
+ and no office program involved.
14
+
15
+ ## What you need
16
+
17
+ | Thing | What it is | Where it comes from |
18
+ | --- | --- | --- |
19
+ | Template | A `.pptx` file with sample slides | Your designer, or your organization's standard deck |
20
+ | Manifest | JSON describing the template | Written once per template: by `draftManifest` (see [template-studio.md](template-studio.md)) or by hand (see [manifest-format.md](manifest-format.md)) |
21
+ | Deck | JSON describing one new presentation | Written by your code, or by a model through `generateDeck` |
22
+ | Assets | Picture bytes by id | Your icon set, your own pictures, or an image source you plug in |
23
+
24
+ ## 1. Render a deck from JSON (no model)
25
+
26
+ ```ts
27
+ import { readFileSync, writeFileSync } from 'node:fs';
28
+ import { loadTemplate, renderDeck } from 'slotdeck';
29
+ import type { DeckSpec, TemplateManifest } from 'slotdeck';
30
+
31
+ const manifest = JSON.parse(readFileSync('template.manifest.json', 'utf8')) as TemplateManifest;
32
+ const template = loadTemplate(readFileSync('template.pptx'), manifest); // do this once and keep it
33
+
34
+ const deck: DeckSpec = {
35
+ version: 1,
36
+ title: 'Quarterly review',
37
+ fields: { organizationDepartment: 'Acme | Operations' },
38
+ slides: [
39
+ { pattern: 'cover', slots: { title: 'Quarterly review', subtitle: 'What changed since March' } },
40
+ {
41
+ pattern: 'text-and-photo',
42
+ slots: {
43
+ title: 'Deliveries are faster',
44
+ body: ['Median delivery time fell from four days to three.', 'The new depot explains most of it.'],
45
+ photo: { asset: 'depot', alt: 'The new depot' },
46
+ },
47
+ },
48
+ { pattern: 'closing', slots: { title: 'Thank you' } },
49
+ ],
50
+ };
51
+
52
+ const { pptx, warnings } = renderDeck(template, deck, {
53
+ assets: { depot: readFileSync('depot.jpg') },
54
+ layout: 'adaptive', // optional slots that the deck leaves out are removed from the slide
55
+ });
56
+ for (const warning of warnings) console.log(`slide ${warning.slide} ${warning.slot ?? ''}: ${warning.message}`);
57
+ writeFileSync('review.pptx', pptx);
58
+ ```
59
+
60
+ The pattern ids (`cover`, `text-and-photo`, `closing`) and slot names (`title`, `body`, `photo`) are whatever
61
+ the manifest defines. To list them:
62
+
63
+ ```ts
64
+ for (const pattern of manifest.patterns) {
65
+ console.log(pattern.id, '-', pattern.description);
66
+ for (const slot of pattern.slots) console.log(' ', slot.name, slot.kind, slot.empty ?? 'required');
67
+ }
68
+ ```
69
+
70
+ If the deck does not fit the manifest (unknown pattern, a required slot without a value, a picture whose
71
+ asset was not passed), `renderDeck` throws a `DeckValidationError` whose `issues` array says what is wrong. Call
72
+ `validateDeck` first if you want the list without an exception. See [rendering.md](rendering.md).
73
+
74
+ ## 2. Generate a deck with a language model
75
+
76
+ ```ts
77
+ import { generateDeck, loadTemplate } from 'slotdeck';
78
+ import type { LlmProvider } from 'slotdeck';
79
+
80
+ // Any model works: the only requirement is this one method. See providers.md.
81
+ const llm: LlmProvider = {
82
+ async generateJson({ system, prompt, schema }) {
83
+ return await myModel.completeAsJson({ system, prompt, schema }); // must resolve with a parsed JSON value
84
+ },
85
+ };
86
+
87
+ const { pptx, deck, warnings, outline } = await generateDeck(
88
+ template,
89
+ {
90
+ prompt: 'Quarterly review for the operations team: deliveries got faster, returns fell, two depots opened.',
91
+ slideCount: 6,
92
+ imageDensity: 'none', // no image source was given, so plan without photos
93
+ fields: { organizationDepartment: 'Acme | Operations' },
94
+ },
95
+ { llm, layout: 'adaptive' },
96
+ );
97
+ ```
98
+
99
+ Keep the returned `deck`: it is what you store, show, edit and render again. See [generating.md](generating.md).
100
+
101
+ With the included Gemini adapter instead of your own provider:
102
+
103
+ ```ts
104
+ import { gemini, geminiImages } from 'slotdeck';
105
+
106
+ const llm = gemini({ model: 'gemini-3.8-flash', apiKey: process.env.GEMINI_API_KEY });
107
+ const images = geminiImages({ model: 'gemini-3.1-flash-image', apiKey: process.env.GEMINI_API_KEY });
108
+ ```
109
+
110
+ ## 3. Change a deck by instruction
111
+
112
+ ```ts
113
+ import { editDeck } from 'slotdeck';
114
+
115
+ const edited = await editDeck(template, deck, 'Shorten slide 2 and add a slide about returns before the end', { llm });
116
+ // edited.deck, edited.pptx, edited.summary, edited.rewritten
117
+ ```
118
+
119
+ ## 4. Describe a new template
120
+
121
+ ```ts
122
+ import { checkManifest, draftManifest, loadTemplate, renderDeck, sampleDeck } from 'slotdeck';
123
+
124
+ const { manifest, skipped, issues } = await draftManifest(readFileSync('new-template.pptx'), { llm, name: 'new-template' });
125
+ const draft = loadTemplate(readFileSync('new-template.pptx'), manifest);
126
+ console.log(checkManifest(draft)); // [] when the manifest matches the file
127
+
128
+ // A deck that uses every pattern once with every slot full: render it and look at it.
129
+ const { deck: sample, assets } = sampleDeck(draft);
130
+ writeFileSync('sample.pptx', renderDeck(draft, sample, { assets }).pptx);
131
+ ```
132
+
133
+ A drafted manifest is a starting point that a person reviews. See [template-studio.md](template-studio.md).
134
+
135
+ ## Where to go next
136
+
137
+ - The two JSON formats: [deck-format.md](deck-format.md), [manifest-format.md](manifest-format.md)
138
+ - Using it inside a chat assistant: [chat-tools.md](chat-tools.md)
139
+ - Connecting an internal model gateway: [providers.md](providers.md)
@@ -0,0 +1,83 @@
1
+ # Languages and reading direction
2
+
3
+ A template is designed for one reading direction (`direction` in the manifest: `ltr` or `rtl`). A deck states
4
+ its own (`direction` in the deck, defaulting to the template's). When they differ, the text flips and the layout
5
+ stays.
6
+
7
+ This works both ways: a Hebrew or Arabic deck on a left-to-right template, and an English deck on a
8
+ right-to-left template.
9
+
10
+ ## Setting language and direction
11
+
12
+ In a deck:
13
+
14
+ ```json
15
+ { "version": 1, "language": "he", "direction": "rtl", "slides": [ ... ] }
16
+ ```
17
+
18
+ In a brief for the planner, `language` is enough: the direction follows from it.
19
+
20
+ ```ts
21
+ await generateDeck(template, { prompt: '...', language: 'he' }, options);
22
+ ```
23
+
24
+ `directionOf('he')` returns `'rtl'`; `directionOf('en')` returns `'ltr'`.
25
+
26
+ ## What changes in a deck that reads the other way
27
+
28
+ | Thing | What happens |
29
+ | --- | --- |
30
+ | Paragraph direction | Every paragraph gets the direction of its own text, from its first letter. A Hebrew deck may hold an English line, and that line is left to right. Text with no letters ("78%") keeps the template paragraph's direction. |
31
+ | Alignment | Left and right alignment are swapped (`alignOnDirectionChange: "flip"`, the default). A paragraph that states no alignment counts as left-aligned. Centred text stays centred. |
32
+ | Rows and grids of items | Filled mirror-wise within each line, so the deck's first item is where its readers start: at the right in a right-to-left deck. Shapes do not move; the values go to the mirrored positions. |
33
+ | Tables | Run in the deck's direction, with text aligned to its start. |
34
+ | Charts | Labels are set right to left. |
35
+ | Run language | Right-to-left text is marked with a right-to-left language. |
36
+ | Shapes | Do not move. A photo on the right stays on the right. Bullets and their indent follow the paragraph direction automatically. |
37
+
38
+ ## Keeping alignment
39
+
40
+ Mirroring alignment suits templates whose text boxes are as wide as the area they belong to. It does not suit a
41
+ template whose text boxes share one edge but not the other: flipped text would then start at uneven positions or
42
+ run into a neighbouring shape.
43
+
44
+ - For the whole template: `"alignOnDirectionChange": "keep"` in the manifest.
45
+ - For one slot: `"alignOnDirectionChange": "keep"` (or `"flip"`) on that text slot.
46
+
47
+ A common case for a per-slot `keep` is a line next to a logo: it should stay beside the logo in either
48
+ direction.
49
+
50
+ ## Why runs are marked with a language
51
+
52
+ PowerPoint decides where punctuation and spaces go by the language of the run they are in, not only by the
53
+ paragraph's direction. In a run marked as English, the text
54
+
55
+ ```
56
+ שאלות: name@example.org
57
+ ```
58
+
59
+ is displayed with the colon on the far side of the address. slotdeck therefore marks right-to-left runs with a
60
+ right-to-left language (`he-IL` for Hebrew, `ar-SA` for Arabic) unless the template already gave the run one.
61
+ You do not need to do anything for this.
62
+
63
+ ## Fonts
64
+
65
+ Text keeps the template's fonts. If the template's font has no glyphs for the deck's script, PowerPoint
66
+ substitutes another font. For a template that will be used in two scripts, set a complex-script font on its text
67
+ (in PowerPoint: the font for "complex scripts"), or choose fonts that cover both.
68
+
69
+ ## Mirrored item order in the deck JSON
70
+
71
+ The deck always numbers items in its own reading order: `card1` is the first card for the reader. You do not
72
+ reverse anything yourself. Validation messages name the slot as the deck wrote it.
73
+
74
+ ## Language of generated text
75
+
76
+ The planner writes in `brief.language`. Slot names and pattern ids stay as the manifest defines them (they are
77
+ identifiers, not content). Translating an existing deck is an edit:
78
+
79
+ ```ts
80
+ await editDeck(template, deck, 'Translate the deck to English', { llm });
81
+ ```
82
+
83
+ This rewrites every slide, sets the direction, and translates deck-level fields.
@@ -0,0 +1,113 @@
1
+ # Layout freedom
2
+
3
+ A deck can depart from the template slide in four bounded ways. In each, the choice is a small value in the deck
4
+ JSON, the manifest says what is allowed, and the code moves the shapes. Nothing is ever positioned by a model.
5
+
6
+ ## 1. A different number of items
7
+
8
+ A pattern with a `repeat` in the manifest has a row or grid of like items: cards, figures, steps. The deck says
9
+ how many with `items`.
10
+
11
+ Manifest:
12
+
13
+ ```json
14
+ "repeat": {
15
+ "item": "card",
16
+ "slots": ["card{n}Icon", "card{n}Heading", "card{n}Text"],
17
+ "shapes": [["6"], ["12"], ["18"], ["24"]],
18
+ "min": 2,
19
+ "max": 4
20
+ }
21
+ ```
22
+
23
+ Deck:
24
+
25
+ ```json
26
+ { "pattern": "icon-cards", "items": 3, "slots": { "title": "...", "card1Icon": {"asset": "clock"}, "card1Heading": "...", "card1Text": "...", "card2Icon": {"asset": "shield"}, "card2Heading": "...", "card2Text": "...", "card3Icon": {"asset": "people"}, "card3Heading": "...", "card3Text": "..." } }
27
+ ```
28
+
29
+ What the renderer does:
30
+
31
+ - **Fewer items than drawn:** the surplus items' shapes are removed.
32
+ - **More items than drawn:** the last item is copied. A copy's slots have the next numbers (`card5Icon`,
33
+ `card5Heading`, ...) and take the last drawn item's limits.
34
+ - **Spacing:** the items are laid out again around the centre of the area the row occupied. They keep the
35
+ template's spacing when there is room and move closer together when there is not. With a `span`, the row may
36
+ spread out to those limits.
37
+ - Without `items`, the slide has the number of items drawn on the template slide.
38
+
39
+ An `items` value outside `min` to `max` is an error:
40
+ `pattern "icon-cards" takes 2 to 4 cards, not 5`.
41
+
42
+ How many items fit is a property of the template: `changeManifest` refuses a `max` that does not fit side by
43
+ side and says the most that do (see [template-studio.md](template-studio.md)).
44
+
45
+ ## 2. Grids
46
+
47
+ With `columns` in the `repeat`, the items form a grid. `columns` is how many items one line holds on the
48
+ template slide.
49
+
50
+ - Items fill lines in reading order and use as few lines as the count needs.
51
+ - They are spread evenly over those lines: in a three-column grid, four items make two lines of two (not three
52
+ and one), five make three and two, three make one line.
53
+ - Each line is centred, and a single line moves to the middle of the grid's area.
54
+ - A grid cannot hold more items than it draws.
55
+
56
+ ## 3. Larger text
57
+
58
+ A deck can ask for larger body text:
59
+
60
+ ```json
61
+ { "version": 1, "textScale": 1.25, "slides": [ ... ] }
62
+ ```
63
+
64
+ or, on a brief for the planner, `textSize: 'large'` (1.25) or `'x-large'` (1.5). The named sizes are in
65
+ `TEXT_SIZES`: `{ normal: 1, large: 1.25, 'x-large': 1.5 }`.
66
+
67
+ - It applies to **body text**: by default, text slots whose template size is 12 pt or under and that are not
68
+ deck fields. Titles and large figures keep their size. A slot can opt in or out with `scales` in the
69
+ manifest.
70
+ - Boxes do not grow, so larger text fits less. The character budget of a scaled slot is divided by the square
71
+ of the scale (a 300-character slot holds 192 at 1.25), and the paragraph budget by the scale. The planner
72
+ shows the model the reduced budgets, so it writes less.
73
+ - Through `editDeck`, "make the text larger" sets the scale and rewrites only the slides whose text no longer
74
+ fits.
75
+
76
+ ## 4. Derived patterns
77
+
78
+ Several patterns may point at the same template slide and use it differently. A derived pattern is made by
79
+ copying a pattern and changing some of its slots:
80
+
81
+ | Change | Manifest property | Example |
82
+ | --- | --- | --- |
83
+ | Remove a shape always | `"empty": "drop"` on a slot that points at it | A text-only slide from a slide with a photo |
84
+ | Move or resize a text box | `box` on the text slot | Widen the text over the area the photo left free |
85
+ | Set a font size | `fontSize` | Larger body text for a slide with little on it |
86
+ | Space paragraphs | `paragraphGap` | Air between two or three short paragraphs |
87
+ | Give a picture another area | `box` on the image slot | A wide diagram in place of a small sample chart |
88
+ | A native chart in place of a picture | a `chart` slot on the picture's shape | The chart slide with editable data |
89
+
90
+ Example: a text-only pattern derived from a photo pattern.
91
+
92
+ ```json
93
+ {
94
+ "id": "text-only",
95
+ "sourceSlide": 2,
96
+ "description": "A title, an optional supporting line and two or three short paragraphs, with no picture.",
97
+ "slots": [
98
+ { "name": "photo", "kind": "image", "imageKind": "graphic", "shapeId": "2", "empty": "drop" },
99
+ { "name": "title", "kind": "text", "shapeId": "5", "size": 34, "maxChars": 83, "box": { "x": 0.0375, "y": 0.2, "w": 0.925, "h": 0.187 } },
100
+ { "name": "body", "kind": "text", "shapeId": "7", "size": 18, "maxChars": 331, "maxParagraphs": 3, "fontSize": 20, "paragraphGap": 12, "box": { "x": 0.0375, "y": 0.4933, "w": 0.72, "h": 0.3867 } }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ With `changeManifest` this is three operations (`copyPattern`, `addSlot` with role `decoration`, `setSlot` with
106
+ a `box`), and the budgets are worked out for you: see [template-studio.md](template-studio.md).
107
+
108
+ ## What is not possible
109
+
110
+ - Moving a shape to an arbitrary place from a deck. Positions come only from the template and the manifest.
111
+ - Swapping the side of a photo.
112
+ - A grid with more items than the template slide draws.
113
+ - New kinds of slides that the template has no sample for.