@liustack/pptwise 0.22.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/README.zh-CN.md +136 -0
  4. package/cordis.patch.yml +5 -0
  5. package/dist/chunk-3ZUKISTY.js +114 -0
  6. package/dist/chunk-3ZUKISTY.js.map +1 -0
  7. package/dist/chunk-M35M4QUC.js +1167 -0
  8. package/dist/chunk-M35M4QUC.js.map +1 -0
  9. package/dist/chunk-VUOLBHD7.js +19 -0
  10. package/dist/chunk-VUOLBHD7.js.map +1 -0
  11. package/dist/chunk-WL5KWYKS.js +49762 -0
  12. package/dist/chunk-WL5KWYKS.js.map +1 -0
  13. package/dist/cli.js +4753 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/index.d.ts +4224 -0
  16. package/dist/index.js +99 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/node.d.ts +7 -0
  19. package/dist/node.js +11 -0
  20. package/dist/node.js.map +1 -0
  21. package/dist/pixel-audit-H5K6JK3X.js +218 -0
  22. package/dist/pixel-audit-H5K6JK3X.js.map +1 -0
  23. package/dist/registry-C0GJH7ZT.d.ts +46 -0
  24. package/dsh/client.js +1398 -0
  25. package/dsh/index.js +141 -0
  26. package/dsh/preview-tool.js +1931 -0
  27. package/dsh/spawnHidden.js +109 -0
  28. package/package.json +113 -0
  29. package/skills/pptwise/SKILL.md +100 -0
  30. package/skills/pptwise/SKILL.zh-CN.md +102 -0
  31. package/skills/pptwise/references/branding.md +18 -0
  32. package/skills/pptwise/references/branding.zh-CN.md +21 -0
  33. package/skills/pptwise/references/components.md +35 -0
  34. package/skills/pptwise/references/components.zh-CN.md +40 -0
  35. package/skills/pptwise/references/density.md +17 -0
  36. package/skills/pptwise/references/density.zh-CN.md +22 -0
  37. package/skills/pptwise/references/images.md +42 -0
  38. package/skills/pptwise/references/images.zh-CN.md +47 -0
  39. package/skills/pptwise/references/layouts.md +37 -0
  40. package/skills/pptwise/references/layouts.zh-CN.md +42 -0
  41. package/skills/pptwise/references/spec.md +107 -0
  42. package/skills/pptwise/references/spec.zh-CN.md +112 -0
  43. package/skills/pptwise/references/validate.md +82 -0
  44. package/skills/pptwise/references/validate.zh-CN.md +87 -0
  45. package/skills/pptwise/scripts/run.ps1 +192 -0
  46. package/skills/pptwise/scripts/run.sh +229 -0
@@ -0,0 +1,4224 @@
1
+ import { z } from 'zod';
2
+ export { P as PptwisePlatform, R as RasterizedImage, i as installPlatform } from './registry-C0GJH7ZT.js';
3
+
4
+ declare const VERSION = "0.22.0";
5
+
6
+ /** Error class thrown by pptwise's public API surface (validation, rendering, export). */
7
+ declare class PptwiseError extends Error {
8
+ }
9
+
10
+ declare const STRATEGY_VALUES: readonly ["pyramid", "storytelling", "instructional", "showcase", "briefing"];
11
+ declare const PACING_VALUES: readonly ["dense", "balanced", "spacious"];
12
+ declare const AUDIENCE_VALUES: readonly ["executive", "technical", "customer", "public"];
13
+ declare const BEAT_VALUES: readonly ["anchor", "dense", "breathing"];
14
+
15
+ /**
16
+ * The v4 IR schema root: theme/meta/assets/brand/branding/background/slide/narrative
17
+ * and the top-level `PptxIRSchema` a deck document parses against
18
+ * (`parsePptxIR`). The frozen v3 shape lives in `./legacy-v3.ts`, kept only
19
+ * for `migrateIrV3ToV4`'s input parsing (spec §9.3).
20
+ *
21
+ * **The `ComponentSchema` union below is a pure aggregator (src domain reorg
22
+ * wave 2, spec §4.3), same discipline as `src/svg/layouts/registry.ts`'s T1d
23
+ * precedent.** Every one of the component schemas used to live here as a
24
+ * literal `z.object({...})` entry directly inside the `discriminatedUnion`
25
+ * array. Each now lives in its own `src/ir/components/<name>.ts` domain file
26
+ * instead (schema + field aliases + render-trait declaration together, spec
27
+ * §4.1) — imported below and referenced by name, in the exact union position
28
+ * its literal used to occupy, so "take one component away whole" is a
29
+ * single-file operation instead of an edit split across this file,
30
+ * `field-aliases.ts`, and `component-traits.ts`. This file's own job for
31
+ * components is now purely computational: import every component schema, construct
32
+ * `ComponentSchema` via `z.discriminatedUnion`, and derive `COMPONENT_TYPES`
33
+ * from the result — never a hand-copied literal, never a re-export relay. A
34
+ * handful of schema fragments genuinely shared by ≥2 components (only the
35
+ * icon-name enum, as of this wave) live in `src/ir/components/shared.ts`
36
+ * instead of any one domain file; every other named sub-schema that reads as
37
+ * "shared" at a glance (e.g. `GanttItemSchema`, `PestQuadrantSchema`,
38
+ * `SankeyNodeSchema`/`SankeyLinkSchema`) turned out to be single-consumer and
39
+ * moved into its own component's domain file with it.
40
+ *
41
+ * The rest of this module — everything outside the `// ── Components` section
42
+ * — is unrelated to the component-domain split and was never in its scope:
43
+ * background/theme/meta/assets/brand/branding/slide/narrative and the top-level
44
+ * `PptxIRSchema` are genuinely this file's own content, not aggregated from
45
+ * elsewhere.
46
+ */
47
+
48
+ declare const BUILTIN_THEME_IDS: readonly ["consulting", "enterprise", "academic", "insight", "campaign", "classroom", "ink", "tech", "runway", "journal", "luxe", "heritage", "pulse", "terra", "ember", "vermilion", "crayon", "arena", "museum", "stage", "lecture", "swiss", "memo", "playbill"];
49
+ declare const BackgroundSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
50
+ kind: z.ZodLiteral<"color">;
51
+ value: z.ZodString;
52
+ }, z.core.$strict>, z.ZodObject<{
53
+ kind: z.ZodLiteral<"gradient">;
54
+ from: z.ZodString;
55
+ to: z.ZodString;
56
+ direction: z.ZodOptional<z.ZodEnum<{
57
+ tb: "tb";
58
+ lr: "lr";
59
+ diagonal: "diagonal";
60
+ }>>;
61
+ }, z.core.$strict>, z.ZodObject<{
62
+ kind: z.ZodLiteral<"asset">;
63
+ asset_id: z.ZodString;
64
+ overlay: z.ZodOptional<z.ZodObject<{
65
+ color: z.ZodString;
66
+ opacity: z.ZodNumber;
67
+ }, z.core.$strict>>;
68
+ fit: z.ZodOptional<z.ZodEnum<{
69
+ contain: "contain";
70
+ cover: "cover";
71
+ }>>;
72
+ }, z.core.$strict>], "kind">;
73
+ /**
74
+ * Style-token override (theme.style): deep-partial palette/fonts/shape
75
+ * merged over the built-in theme (see themes/index.ts resolveStyle). Scope is
76
+ * deliberately palette-level (spec §11): no defaultBackgrounds or manifest
77
+ * overrides. gapScale / typeScale ranges mirror the documented sane ranges
78
+ * in themes/tokens.ts StyleShape.
79
+ */
80
+ declare const StyleOverrideSchema: z.ZodObject<{
81
+ colors: z.ZodOptional<z.ZodObject<{
82
+ bg: z.ZodOptional<z.ZodString>;
83
+ surface: z.ZodOptional<z.ZodString>;
84
+ panel: z.ZodOptional<z.ZodString>;
85
+ primary: z.ZodOptional<z.ZodString>;
86
+ accent: z.ZodOptional<z.ZodString>;
87
+ text: z.ZodOptional<z.ZodString>;
88
+ muted: z.ZodOptional<z.ZodString>;
89
+ border: z.ZodOptional<z.ZodString>;
90
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
91
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
92
+ cardStroke: z.ZodOptional<z.ZodString>;
93
+ }, z.core.$strict>>;
94
+ fonts: z.ZodOptional<z.ZodObject<{
95
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
96
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
97
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
98
+ }, z.core.$strict>>;
99
+ shape: z.ZodOptional<z.ZodObject<{
100
+ radius: z.ZodOptional<z.ZodNumber>;
101
+ gapScale: z.ZodOptional<z.ZodNumber>;
102
+ typeScale: z.ZodOptional<z.ZodNumber>;
103
+ }, z.core.$strict>>;
104
+ }, z.core.$strict>;
105
+ type StyleOverride = z.infer<typeof StyleOverrideSchema>;
106
+ /**
107
+ * Brand (logical slide-master) config: branding behavior owned by a theme.
108
+ * W1 scope was exactly the two flags migrated from the old theme-manifest footer flags;
109
+ * the ink v3 redesign (2026-08-18) added a third, orthogonal one.
110
+ * Single source of truth — the TS type is inferred, never hand-written.
111
+ *
112
+ * All three are independent switches, not a ladder: a theme may set any
113
+ * combination, and each names exactly one piece of brand footer. They are
114
+ * deliberately not collapsed into one "footer style" enum — a theme that
115
+ * draws its own divider is a different situation from one whose motif
116
+ * already carries the org/date, and merging them would force one to imply
117
+ * the other.
118
+ */
119
+ declare const BrandConfigSchema: z.ZodObject<{
120
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
121
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
122
+ suppressFooterMeta: z.ZodOptional<z.ZodBoolean>;
123
+ }, z.core.$strict>;
124
+ type BrandConfig = z.infer<typeof BrandConfigSchema>;
125
+ declare const ThemeSchema: z.ZodObject<{
126
+ id: z.ZodDefault<z.ZodString>;
127
+ style: z.ZodOptional<z.ZodObject<{
128
+ colors: z.ZodOptional<z.ZodObject<{
129
+ bg: z.ZodOptional<z.ZodString>;
130
+ surface: z.ZodOptional<z.ZodString>;
131
+ panel: z.ZodOptional<z.ZodString>;
132
+ primary: z.ZodOptional<z.ZodString>;
133
+ accent: z.ZodOptional<z.ZodString>;
134
+ text: z.ZodOptional<z.ZodString>;
135
+ muted: z.ZodOptional<z.ZodString>;
136
+ border: z.ZodOptional<z.ZodString>;
137
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
138
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
139
+ cardStroke: z.ZodOptional<z.ZodString>;
140
+ }, z.core.$strict>>;
141
+ fonts: z.ZodOptional<z.ZodObject<{
142
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
144
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
145
+ }, z.core.$strict>>;
146
+ shape: z.ZodOptional<z.ZodObject<{
147
+ radius: z.ZodOptional<z.ZodNumber>;
148
+ gapScale: z.ZodOptional<z.ZodNumber>;
149
+ typeScale: z.ZodOptional<z.ZodNumber>;
150
+ }, z.core.$strict>>;
151
+ }, z.core.$strict>>;
152
+ brand: z.ZodOptional<z.ZodObject<{
153
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
154
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
155
+ suppressFooterMeta: z.ZodOptional<z.ZodBoolean>;
156
+ }, z.core.$strict>>;
157
+ }, z.core.$strict>;
158
+ declare const MetaSchema: z.ZodObject<{
159
+ organization: z.ZodOptional<z.ZodString>;
160
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
161
+ name: z.ZodString;
162
+ role: z.ZodOptional<z.ZodString>;
163
+ org: z.ZodOptional<z.ZodString>;
164
+ }, z.core.$strict>>>;
165
+ date: z.ZodOptional<z.ZodString>;
166
+ version: z.ZodOptional<z.ZodString>;
167
+ confidentiality: z.ZodOptional<z.ZodEnum<{
168
+ public: "public";
169
+ internal: "internal";
170
+ confidential: "confidential";
171
+ restricted: "restricted";
172
+ }>>;
173
+ contact: z.ZodOptional<z.ZodObject<{
174
+ name: z.ZodOptional<z.ZodString>;
175
+ email: z.ZodOptional<z.ZodString>;
176
+ phone: z.ZodOptional<z.ZodString>;
177
+ website: z.ZodOptional<z.ZodString>;
178
+ }, z.core.$strict>>;
179
+ copyright: z.ZodOptional<z.ZodString>;
180
+ animation: z.ZodOptional<z.ZodObject<{
181
+ transition: z.ZodOptional<z.ZodEnum<{
182
+ push: "push";
183
+ none: "none";
184
+ fade: "fade";
185
+ wipe: "wipe";
186
+ }>>;
187
+ elements: z.ZodOptional<z.ZodEnum<{
188
+ none: "none";
189
+ auto: "auto";
190
+ }>>;
191
+ }, z.core.$strict>>;
192
+ }, z.core.$strict>;
193
+ declare const AssetsSchema: z.ZodObject<{
194
+ images: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
195
+ src: z.ZodString;
196
+ alt: z.ZodOptional<z.ZodString>;
197
+ error: z.ZodOptional<z.ZodString>;
198
+ }, z.core.$strict>>>;
199
+ }, z.core.$strict>;
200
+ declare const ComponentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
201
+ type: z.ZodLiteral<"bullets">;
202
+ items: z.ZodArray<z.ZodString>;
203
+ style: z.ZodOptional<z.ZodEnum<{
204
+ default: "default";
205
+ checklist: "checklist";
206
+ numbered: "numbered";
207
+ plain: "plain";
208
+ divided: "divided";
209
+ }>>;
210
+ }, z.core.$strict>, z.ZodObject<{
211
+ type: z.ZodLiteral<"paragraph">;
212
+ text: z.ZodString;
213
+ }, z.core.$strict>, z.ZodObject<{
214
+ type: z.ZodLiteral<"quote">;
215
+ text: z.ZodString;
216
+ attribution: z.ZodOptional<z.ZodString>;
217
+ }, z.core.$strict>, z.ZodObject<{
218
+ type: z.ZodLiteral<"callout">;
219
+ variant: z.ZodEnum<{
220
+ info: "info";
221
+ warn: "warn";
222
+ tip: "tip";
223
+ }>;
224
+ text: z.ZodString;
225
+ icon: z.ZodOptional<z.ZodEnum<{
226
+ [x: string]: string;
227
+ }>>;
228
+ }, z.core.$strict>, z.ZodObject<{
229
+ type: z.ZodLiteral<"code">;
230
+ language: z.ZodString;
231
+ code: z.ZodString;
232
+ }, z.core.$strict>, z.ZodObject<{
233
+ type: z.ZodLiteral<"kpi_cards">;
234
+ items: z.ZodArray<z.ZodObject<{
235
+ value: z.ZodString;
236
+ unit: z.ZodOptional<z.ZodString>;
237
+ label: z.ZodString;
238
+ delta: z.ZodOptional<z.ZodEnum<{
239
+ flat: "flat";
240
+ up: "up";
241
+ down: "down";
242
+ }>>;
243
+ icon: z.ZodOptional<z.ZodEnum<{
244
+ [x: string]: string;
245
+ }>>;
246
+ source: z.ZodOptional<z.ZodString>;
247
+ }, z.core.$strict>>;
248
+ }, z.core.$strict>, z.ZodObject<{
249
+ type: z.ZodLiteral<"chart">;
250
+ chart_type: z.ZodEnum<{
251
+ line: "line";
252
+ donut: "donut";
253
+ dumbbell: "dumbbell";
254
+ funnel: "funnel";
255
+ gauge: "gauge";
256
+ bar: "bar";
257
+ pie: "pie";
258
+ scatter: "scatter";
259
+ area: "area";
260
+ }>;
261
+ direction: z.ZodOptional<z.ZodEnum<{
262
+ horizontal: "horizontal";
263
+ vertical: "vertical";
264
+ }>>;
265
+ style: z.ZodOptional<z.ZodEnum<{
266
+ donut: "donut";
267
+ }>>;
268
+ center_total: z.ZodOptional<z.ZodBoolean>;
269
+ gauge: z.ZodOptional<z.ZodObject<{
270
+ min: z.ZodOptional<z.ZodNumber>;
271
+ max: z.ZodOptional<z.ZodNumber>;
272
+ }, z.core.$strict>>;
273
+ axes: z.ZodOptional<z.ZodObject<{
274
+ x_title: z.ZodOptional<z.ZodString>;
275
+ y_title: z.ZodOptional<z.ZodString>;
276
+ show_grid: z.ZodOptional<z.ZodBoolean>;
277
+ }, z.core.$strict>>;
278
+ series: z.ZodArray<z.ZodObject<{
279
+ name: z.ZodString;
280
+ data: z.ZodArray<z.ZodObject<{
281
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
282
+ y: z.ZodNumber;
283
+ size: z.ZodOptional<z.ZodNumber>;
284
+ }, z.core.$strict>>;
285
+ }, z.core.$strict>>;
286
+ }, z.core.$strict>, z.ZodObject<{
287
+ type: z.ZodLiteral<"flowchart">;
288
+ nodes: z.ZodArray<z.ZodObject<{
289
+ id: z.ZodString;
290
+ label: z.ZodString;
291
+ kind: z.ZodOptional<z.ZodEnum<{
292
+ rect: "rect";
293
+ diamond: "diamond";
294
+ round: "round";
295
+ }>>;
296
+ }, z.core.$strict>>;
297
+ edges: z.ZodArray<z.ZodObject<{
298
+ from: z.ZodString;
299
+ to: z.ZodString;
300
+ label: z.ZodOptional<z.ZodString>;
301
+ }, z.core.$strict>>;
302
+ direction: z.ZodOptional<z.ZodEnum<{
303
+ TB: "TB";
304
+ TD: "TD";
305
+ BT: "BT";
306
+ LR: "LR";
307
+ RL: "RL";
308
+ }>>;
309
+ }, z.core.$strict>, z.ZodObject<{
310
+ type: z.ZodLiteral<"architecture">;
311
+ layers: z.ZodArray<z.ZodObject<{
312
+ title: z.ZodString;
313
+ items: z.ZodArray<z.ZodString>;
314
+ }, z.core.$strict>>;
315
+ direction: z.ZodOptional<z.ZodEnum<{
316
+ top_down: "top_down";
317
+ bottom_up: "bottom_up";
318
+ }>>;
319
+ }, z.core.$strict>, z.ZodObject<{
320
+ type: z.ZodLiteral<"timeline">;
321
+ layout: z.ZodOptional<z.ZodEnum<{
322
+ horizontal: "horizontal";
323
+ vertical: "vertical";
324
+ }>>;
325
+ milestones: z.ZodArray<z.ZodObject<{
326
+ date: z.ZodString;
327
+ title: z.ZodString;
328
+ desc: z.ZodOptional<z.ZodString>;
329
+ highlight: z.ZodOptional<z.ZodBoolean>;
330
+ }, z.core.$strict>>;
331
+ }, z.core.$strict>, z.ZodObject<{
332
+ type: z.ZodLiteral<"comparison">;
333
+ columns: z.ZodArray<z.ZodString>;
334
+ rows: z.ZodArray<z.ZodObject<{
335
+ label: z.ZodString;
336
+ cells: z.ZodArray<z.ZodString>;
337
+ }, z.core.$strict>>;
338
+ }, z.core.$strict>, z.ZodObject<{
339
+ type: z.ZodLiteral<"icon_cards">;
340
+ items: z.ZodArray<z.ZodObject<{
341
+ icon: z.ZodEnum<{
342
+ [x: string]: string;
343
+ }>;
344
+ title: z.ZodString;
345
+ text: z.ZodString;
346
+ }, z.core.$strict>>;
347
+ }, z.core.$strict>, z.ZodObject<{
348
+ type: z.ZodLiteral<"row_cards">;
349
+ items: z.ZodArray<z.ZodObject<{
350
+ icon: z.ZodOptional<z.ZodEnum<{
351
+ [x: string]: string;
352
+ }>>;
353
+ title: z.ZodString;
354
+ text: z.ZodOptional<z.ZodString>;
355
+ sub: z.ZodOptional<z.ZodString>;
356
+ highlight: z.ZodOptional<z.ZodBoolean>;
357
+ }, z.core.$strict>>;
358
+ }, z.core.$strict>, z.ZodObject<{
359
+ type: z.ZodLiteral<"steps">;
360
+ items: z.ZodArray<z.ZodObject<{
361
+ title: z.ZodString;
362
+ text: z.ZodString;
363
+ }, z.core.$strict>>;
364
+ }, z.core.$strict>, z.ZodObject<{
365
+ type: z.ZodLiteral<"rings">;
366
+ items: z.ZodArray<z.ZodObject<{
367
+ label: z.ZodString;
368
+ desc: z.ZodOptional<z.ZodString>;
369
+ }, z.core.$strict>>;
370
+ }, z.core.$strict>, z.ZodObject<{
371
+ type: z.ZodLiteral<"numbered_cards">;
372
+ items: z.ZodArray<z.ZodObject<{
373
+ title: z.ZodString;
374
+ text: z.ZodOptional<z.ZodString>;
375
+ sub: z.ZodOptional<z.ZodString>;
376
+ }, z.core.$strict>>;
377
+ }, z.core.$strict>, z.ZodObject<{
378
+ type: z.ZodLiteral<"roadmap">;
379
+ items: z.ZodArray<z.ZodObject<{
380
+ title: z.ZodString;
381
+ period: z.ZodOptional<z.ZodString>;
382
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
383
+ label: z.ZodString;
384
+ value: z.ZodString;
385
+ }, z.core.$strict>>>;
386
+ }, z.core.$strict>>;
387
+ }, z.core.$strict>, z.ZodObject<{
388
+ type: z.ZodLiteral<"matrix">;
389
+ x_title: z.ZodOptional<z.ZodString>;
390
+ y_title: z.ZodOptional<z.ZodString>;
391
+ cols: z.ZodNumber;
392
+ items: z.ZodArray<z.ZodObject<{
393
+ title: z.ZodString;
394
+ tag: z.ZodOptional<z.ZodString>;
395
+ tone: z.ZodOptional<z.ZodEnum<{
396
+ info: "info";
397
+ neutral: "neutral";
398
+ accent: "accent";
399
+ }>>;
400
+ }, z.core.$strict>>;
401
+ }, z.core.$strict>, z.ZodObject<{
402
+ type: z.ZodLiteral<"insight_panel">;
403
+ title: z.ZodString;
404
+ rows: z.ZodArray<z.ZodObject<{
405
+ label: z.ZodString;
406
+ text: z.ZodString;
407
+ }, z.core.$strict>>;
408
+ footnote: z.ZodOptional<z.ZodString>;
409
+ }, z.core.$strict>, z.ZodObject<{
410
+ type: z.ZodLiteral<"verdict_banner">;
411
+ text: z.ZodString;
412
+ tone: z.ZodEnum<{
413
+ positive: "positive";
414
+ warning: "warning";
415
+ neutral: "neutral";
416
+ }>;
417
+ icon: z.ZodOptional<z.ZodEnum<{
418
+ [x: string]: string;
419
+ }>>;
420
+ }, z.core.$strict>, z.ZodObject<{
421
+ type: z.ZodLiteral<"citation">;
422
+ sources: z.ZodArray<z.ZodObject<{
423
+ label: z.ZodString;
424
+ url: z.ZodOptional<z.ZodString>;
425
+ ref: z.ZodOptional<z.ZodString>;
426
+ }, z.core.$strict>>;
427
+ }, z.core.$strict>, z.ZodObject<{
428
+ type: z.ZodLiteral<"image">;
429
+ asset_id: z.ZodString;
430
+ caption: z.ZodOptional<z.ZodString>;
431
+ fit: z.ZodDefault<z.ZodEnum<{
432
+ contain: "contain";
433
+ cover: "cover";
434
+ }>>;
435
+ }, z.core.$strict>, z.ZodObject<{
436
+ type: z.ZodLiteral<"image_grid">;
437
+ items: z.ZodArray<z.ZodObject<{
438
+ asset_id: z.ZodString;
439
+ caption: z.ZodOptional<z.ZodString>;
440
+ }, z.core.$strict>>;
441
+ emphasis: z.ZodOptional<z.ZodEnum<{
442
+ none: "none";
443
+ first: "first";
444
+ }>>;
445
+ }, z.core.$strict>, z.ZodObject<{
446
+ type: z.ZodLiteral<"image_compare">;
447
+ left: z.ZodObject<{
448
+ asset_id: z.ZodString;
449
+ label: z.ZodString;
450
+ }, z.core.$strict>;
451
+ right: z.ZodObject<{
452
+ asset_id: z.ZodString;
453
+ label: z.ZodString;
454
+ }, z.core.$strict>;
455
+ style: z.ZodOptional<z.ZodEnum<{
456
+ vs: "vs";
457
+ before_after: "before_after";
458
+ }>>;
459
+ }, z.core.$strict>, z.ZodObject<{
460
+ type: z.ZodLiteral<"swot">;
461
+ strengths: z.ZodArray<z.ZodString>;
462
+ weaknesses: z.ZodArray<z.ZodString>;
463
+ opportunities: z.ZodArray<z.ZodString>;
464
+ threats: z.ZodArray<z.ZodString>;
465
+ labels: z.ZodOptional<z.ZodObject<{
466
+ strengths: z.ZodOptional<z.ZodString>;
467
+ weaknesses: z.ZodOptional<z.ZodString>;
468
+ opportunities: z.ZodOptional<z.ZodString>;
469
+ threats: z.ZodOptional<z.ZodString>;
470
+ }, z.core.$strict>>;
471
+ }, z.core.$strict>, z.ZodObject<{
472
+ type: z.ZodLiteral<"bmc">;
473
+ key_partners: z.ZodArray<z.ZodString>;
474
+ key_activities: z.ZodArray<z.ZodString>;
475
+ key_resources: z.ZodArray<z.ZodString>;
476
+ value_propositions: z.ZodArray<z.ZodString>;
477
+ customer_relationships: z.ZodArray<z.ZodString>;
478
+ channels: z.ZodArray<z.ZodString>;
479
+ customer_segments: z.ZodArray<z.ZodString>;
480
+ cost_structure: z.ZodArray<z.ZodString>;
481
+ revenue_streams: z.ZodArray<z.ZodString>;
482
+ }, z.core.$strict>, z.ZodObject<{
483
+ type: z.ZodLiteral<"waterfall">;
484
+ items: z.ZodArray<z.ZodObject<{
485
+ label: z.ZodString;
486
+ value: z.ZodNumber;
487
+ kind: z.ZodOptional<z.ZodEnum<{
488
+ delta: "delta";
489
+ total: "total";
490
+ }>>;
491
+ }, z.core.$strict>>;
492
+ unit: z.ZodOptional<z.ZodString>;
493
+ }, z.core.$strict>, z.ZodObject<{
494
+ type: z.ZodLiteral<"gantt">;
495
+ items: z.ZodArray<z.ZodObject<{
496
+ label: z.ZodString;
497
+ start: z.ZodNumber;
498
+ end: z.ZodNumber;
499
+ }, z.core.$strict>>;
500
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
501
+ }, z.core.$strict>, z.ZodObject<{
502
+ type: z.ZodLiteral<"pest">;
503
+ political: z.ZodObject<{
504
+ title: z.ZodOptional<z.ZodString>;
505
+ items: z.ZodArray<z.ZodString>;
506
+ }, z.core.$strict>;
507
+ economic: z.ZodObject<{
508
+ title: z.ZodOptional<z.ZodString>;
509
+ items: z.ZodArray<z.ZodString>;
510
+ }, z.core.$strict>;
511
+ social: z.ZodObject<{
512
+ title: z.ZodOptional<z.ZodString>;
513
+ items: z.ZodArray<z.ZodString>;
514
+ }, z.core.$strict>;
515
+ technological: z.ZodObject<{
516
+ title: z.ZodOptional<z.ZodString>;
517
+ items: z.ZodArray<z.ZodString>;
518
+ }, z.core.$strict>;
519
+ }, z.core.$strict>, z.ZodObject<{
520
+ type: z.ZodLiteral<"five_forces">;
521
+ rivalry: z.ZodObject<{
522
+ label: z.ZodOptional<z.ZodString>;
523
+ intensity: z.ZodOptional<z.ZodEnum<{
524
+ low: "low";
525
+ medium: "medium";
526
+ high: "high";
527
+ }>>;
528
+ items: z.ZodArray<z.ZodString>;
529
+ }, z.core.$strict>;
530
+ new_entrants: z.ZodObject<{
531
+ label: z.ZodOptional<z.ZodString>;
532
+ intensity: z.ZodOptional<z.ZodEnum<{
533
+ low: "low";
534
+ medium: "medium";
535
+ high: "high";
536
+ }>>;
537
+ items: z.ZodArray<z.ZodString>;
538
+ }, z.core.$strict>;
539
+ supplier_power: z.ZodObject<{
540
+ label: z.ZodOptional<z.ZodString>;
541
+ intensity: z.ZodOptional<z.ZodEnum<{
542
+ low: "low";
543
+ medium: "medium";
544
+ high: "high";
545
+ }>>;
546
+ items: z.ZodArray<z.ZodString>;
547
+ }, z.core.$strict>;
548
+ buyer_power: z.ZodObject<{
549
+ label: z.ZodOptional<z.ZodString>;
550
+ intensity: z.ZodOptional<z.ZodEnum<{
551
+ low: "low";
552
+ medium: "medium";
553
+ high: "high";
554
+ }>>;
555
+ items: z.ZodArray<z.ZodString>;
556
+ }, z.core.$strict>;
557
+ substitutes: z.ZodObject<{
558
+ label: z.ZodOptional<z.ZodString>;
559
+ intensity: z.ZodOptional<z.ZodEnum<{
560
+ low: "low";
561
+ medium: "medium";
562
+ high: "high";
563
+ }>>;
564
+ items: z.ZodArray<z.ZodString>;
565
+ }, z.core.$strict>;
566
+ }, z.core.$strict>, z.ZodObject<{
567
+ type: z.ZodLiteral<"heatmap">;
568
+ x_labels: z.ZodArray<z.ZodString>;
569
+ y_labels: z.ZodArray<z.ZodString>;
570
+ values: z.ZodArray<z.ZodArray<z.ZodNumber>>;
571
+ domain: z.ZodOptional<z.ZodObject<{
572
+ min: z.ZodNumber;
573
+ max: z.ZodNumber;
574
+ }, z.core.$strict>>;
575
+ show_values: z.ZodOptional<z.ZodBoolean>;
576
+ x_title: z.ZodOptional<z.ZodString>;
577
+ y_title: z.ZodOptional<z.ZodString>;
578
+ }, z.core.$strict>, z.ZodObject<{
579
+ type: z.ZodLiteral<"sankey">;
580
+ nodes: z.ZodArray<z.ZodObject<{
581
+ id: z.ZodString;
582
+ label: z.ZodString;
583
+ }, z.core.$strict>>;
584
+ links: z.ZodArray<z.ZodObject<{
585
+ from: z.ZodString;
586
+ to: z.ZodString;
587
+ value: z.ZodNumber;
588
+ }, z.core.$strict>>;
589
+ }, z.core.$strict>, z.ZodObject<{
590
+ type: z.ZodLiteral<"data_table">;
591
+ columns: z.ZodArray<z.ZodObject<{
592
+ key: z.ZodString;
593
+ label: z.ZodString;
594
+ align: z.ZodOptional<z.ZodEnum<{
595
+ left: "left";
596
+ right: "right";
597
+ center: "center";
598
+ }>>;
599
+ }, z.core.$strict>>;
600
+ rows: z.ZodArray<z.ZodObject<{
601
+ cells: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
602
+ emphasis: z.ZodOptional<z.ZodEnum<{
603
+ highlight: "highlight";
604
+ total: "total";
605
+ }>>;
606
+ }, z.core.$strict>>;
607
+ source: z.ZodOptional<z.ZodString>;
608
+ }, z.core.$strict>, z.ZodObject<{
609
+ type: z.ZodLiteral<"device_mockup">;
610
+ device: z.ZodEnum<{
611
+ phone: "phone";
612
+ browser: "browser";
613
+ }>;
614
+ asset_id: z.ZodString;
615
+ caption: z.ZodOptional<z.ZodString>;
616
+ url: z.ZodOptional<z.ZodString>;
617
+ }, z.core.$strict>, z.ZodObject<{
618
+ type: z.ZodLiteral<"cycle">;
619
+ title: z.ZodOptional<z.ZodString>;
620
+ items: z.ZodArray<z.ZodObject<{
621
+ label: z.ZodString;
622
+ description: z.ZodOptional<z.ZodString>;
623
+ }, z.core.$strict>>;
624
+ }, z.core.$strict>, z.ZodObject<{
625
+ type: z.ZodLiteral<"people_cards">;
626
+ title: z.ZodOptional<z.ZodString>;
627
+ people: z.ZodArray<z.ZodObject<{
628
+ name: z.ZodString;
629
+ role: z.ZodOptional<z.ZodString>;
630
+ org: z.ZodOptional<z.ZodString>;
631
+ }, z.core.$strict>>;
632
+ }, z.core.$strict>, z.ZodObject<{
633
+ type: z.ZodLiteral<"tag_row">;
634
+ title: z.ZodOptional<z.ZodString>;
635
+ items: z.ZodArray<z.ZodString>;
636
+ emphasis: z.ZodOptional<z.ZodEnum<{
637
+ none: "none";
638
+ first: "first";
639
+ }>>;
640
+ }, z.core.$strict>], "type">;
641
+ /**
642
+ * All component `type` discriminant values, derived from `ComponentSchema`
643
+ * itself (never hand-copied) so this list can't drift from the union above.
644
+ * Typed as plain `readonly string[]` rather than `Component["type"][]` —
645
+ * every consumer of this list (W5's plan `focus` vocabulary gate,
646
+ * `src/spec/index.ts`) tests membership of an arbitrary author-supplied
647
+ * string, and TS's `Array.includes` is invariant in its element type, so a
648
+ * narrower literal-union type would reject that call at the caller.
649
+ */
650
+ declare const COMPONENT_TYPES: readonly string[];
651
+ declare const SlideSchema: z.ZodObject<{
652
+ type: z.ZodDefault<z.ZodEnum<{
653
+ content: "content";
654
+ cover: "cover";
655
+ chapter: "chapter";
656
+ ending: "ending";
657
+ }>>;
658
+ id: z.ZodOptional<z.ZodString>;
659
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
660
+ layout: z.ZodOptional<z.ZodString>;
661
+ beat: z.ZodOptional<z.ZodEnum<{
662
+ dense: "dense";
663
+ anchor: "anchor";
664
+ breathing: "breathing";
665
+ }>>;
666
+ arrangement: z.ZodOptional<z.ZodEnum<{
667
+ code: "code";
668
+ quote: "quote";
669
+ single: "single";
670
+ two_column: "two_column";
671
+ kpi_focus: "kpi_focus";
672
+ image_focus: "image_focus";
673
+ big_number: "big_number";
674
+ assertion_evidence: "assertion_evidence";
675
+ aside: "aside";
676
+ }>>;
677
+ heading: z.ZodOptional<z.ZodString>;
678
+ subheading: z.ZodOptional<z.ZodString>;
679
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
680
+ type: z.ZodLiteral<"bullets">;
681
+ items: z.ZodArray<z.ZodString>;
682
+ style: z.ZodOptional<z.ZodEnum<{
683
+ default: "default";
684
+ checklist: "checklist";
685
+ numbered: "numbered";
686
+ plain: "plain";
687
+ divided: "divided";
688
+ }>>;
689
+ }, z.core.$strict>, z.ZodObject<{
690
+ type: z.ZodLiteral<"paragraph">;
691
+ text: z.ZodString;
692
+ }, z.core.$strict>, z.ZodObject<{
693
+ type: z.ZodLiteral<"quote">;
694
+ text: z.ZodString;
695
+ attribution: z.ZodOptional<z.ZodString>;
696
+ }, z.core.$strict>, z.ZodObject<{
697
+ type: z.ZodLiteral<"callout">;
698
+ variant: z.ZodEnum<{
699
+ info: "info";
700
+ warn: "warn";
701
+ tip: "tip";
702
+ }>;
703
+ text: z.ZodString;
704
+ icon: z.ZodOptional<z.ZodEnum<{
705
+ [x: string]: string;
706
+ }>>;
707
+ }, z.core.$strict>, z.ZodObject<{
708
+ type: z.ZodLiteral<"code">;
709
+ language: z.ZodString;
710
+ code: z.ZodString;
711
+ }, z.core.$strict>, z.ZodObject<{
712
+ type: z.ZodLiteral<"kpi_cards">;
713
+ items: z.ZodArray<z.ZodObject<{
714
+ value: z.ZodString;
715
+ unit: z.ZodOptional<z.ZodString>;
716
+ label: z.ZodString;
717
+ delta: z.ZodOptional<z.ZodEnum<{
718
+ flat: "flat";
719
+ up: "up";
720
+ down: "down";
721
+ }>>;
722
+ icon: z.ZodOptional<z.ZodEnum<{
723
+ [x: string]: string;
724
+ }>>;
725
+ source: z.ZodOptional<z.ZodString>;
726
+ }, z.core.$strict>>;
727
+ }, z.core.$strict>, z.ZodObject<{
728
+ type: z.ZodLiteral<"chart">;
729
+ chart_type: z.ZodEnum<{
730
+ line: "line";
731
+ donut: "donut";
732
+ dumbbell: "dumbbell";
733
+ funnel: "funnel";
734
+ gauge: "gauge";
735
+ bar: "bar";
736
+ pie: "pie";
737
+ scatter: "scatter";
738
+ area: "area";
739
+ }>;
740
+ direction: z.ZodOptional<z.ZodEnum<{
741
+ horizontal: "horizontal";
742
+ vertical: "vertical";
743
+ }>>;
744
+ style: z.ZodOptional<z.ZodEnum<{
745
+ donut: "donut";
746
+ }>>;
747
+ center_total: z.ZodOptional<z.ZodBoolean>;
748
+ gauge: z.ZodOptional<z.ZodObject<{
749
+ min: z.ZodOptional<z.ZodNumber>;
750
+ max: z.ZodOptional<z.ZodNumber>;
751
+ }, z.core.$strict>>;
752
+ axes: z.ZodOptional<z.ZodObject<{
753
+ x_title: z.ZodOptional<z.ZodString>;
754
+ y_title: z.ZodOptional<z.ZodString>;
755
+ show_grid: z.ZodOptional<z.ZodBoolean>;
756
+ }, z.core.$strict>>;
757
+ series: z.ZodArray<z.ZodObject<{
758
+ name: z.ZodString;
759
+ data: z.ZodArray<z.ZodObject<{
760
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
761
+ y: z.ZodNumber;
762
+ size: z.ZodOptional<z.ZodNumber>;
763
+ }, z.core.$strict>>;
764
+ }, z.core.$strict>>;
765
+ }, z.core.$strict>, z.ZodObject<{
766
+ type: z.ZodLiteral<"flowchart">;
767
+ nodes: z.ZodArray<z.ZodObject<{
768
+ id: z.ZodString;
769
+ label: z.ZodString;
770
+ kind: z.ZodOptional<z.ZodEnum<{
771
+ rect: "rect";
772
+ diamond: "diamond";
773
+ round: "round";
774
+ }>>;
775
+ }, z.core.$strict>>;
776
+ edges: z.ZodArray<z.ZodObject<{
777
+ from: z.ZodString;
778
+ to: z.ZodString;
779
+ label: z.ZodOptional<z.ZodString>;
780
+ }, z.core.$strict>>;
781
+ direction: z.ZodOptional<z.ZodEnum<{
782
+ TB: "TB";
783
+ TD: "TD";
784
+ BT: "BT";
785
+ LR: "LR";
786
+ RL: "RL";
787
+ }>>;
788
+ }, z.core.$strict>, z.ZodObject<{
789
+ type: z.ZodLiteral<"architecture">;
790
+ layers: z.ZodArray<z.ZodObject<{
791
+ title: z.ZodString;
792
+ items: z.ZodArray<z.ZodString>;
793
+ }, z.core.$strict>>;
794
+ direction: z.ZodOptional<z.ZodEnum<{
795
+ top_down: "top_down";
796
+ bottom_up: "bottom_up";
797
+ }>>;
798
+ }, z.core.$strict>, z.ZodObject<{
799
+ type: z.ZodLiteral<"timeline">;
800
+ layout: z.ZodOptional<z.ZodEnum<{
801
+ horizontal: "horizontal";
802
+ vertical: "vertical";
803
+ }>>;
804
+ milestones: z.ZodArray<z.ZodObject<{
805
+ date: z.ZodString;
806
+ title: z.ZodString;
807
+ desc: z.ZodOptional<z.ZodString>;
808
+ highlight: z.ZodOptional<z.ZodBoolean>;
809
+ }, z.core.$strict>>;
810
+ }, z.core.$strict>, z.ZodObject<{
811
+ type: z.ZodLiteral<"comparison">;
812
+ columns: z.ZodArray<z.ZodString>;
813
+ rows: z.ZodArray<z.ZodObject<{
814
+ label: z.ZodString;
815
+ cells: z.ZodArray<z.ZodString>;
816
+ }, z.core.$strict>>;
817
+ }, z.core.$strict>, z.ZodObject<{
818
+ type: z.ZodLiteral<"icon_cards">;
819
+ items: z.ZodArray<z.ZodObject<{
820
+ icon: z.ZodEnum<{
821
+ [x: string]: string;
822
+ }>;
823
+ title: z.ZodString;
824
+ text: z.ZodString;
825
+ }, z.core.$strict>>;
826
+ }, z.core.$strict>, z.ZodObject<{
827
+ type: z.ZodLiteral<"row_cards">;
828
+ items: z.ZodArray<z.ZodObject<{
829
+ icon: z.ZodOptional<z.ZodEnum<{
830
+ [x: string]: string;
831
+ }>>;
832
+ title: z.ZodString;
833
+ text: z.ZodOptional<z.ZodString>;
834
+ sub: z.ZodOptional<z.ZodString>;
835
+ highlight: z.ZodOptional<z.ZodBoolean>;
836
+ }, z.core.$strict>>;
837
+ }, z.core.$strict>, z.ZodObject<{
838
+ type: z.ZodLiteral<"steps">;
839
+ items: z.ZodArray<z.ZodObject<{
840
+ title: z.ZodString;
841
+ text: z.ZodString;
842
+ }, z.core.$strict>>;
843
+ }, z.core.$strict>, z.ZodObject<{
844
+ type: z.ZodLiteral<"rings">;
845
+ items: z.ZodArray<z.ZodObject<{
846
+ label: z.ZodString;
847
+ desc: z.ZodOptional<z.ZodString>;
848
+ }, z.core.$strict>>;
849
+ }, z.core.$strict>, z.ZodObject<{
850
+ type: z.ZodLiteral<"numbered_cards">;
851
+ items: z.ZodArray<z.ZodObject<{
852
+ title: z.ZodString;
853
+ text: z.ZodOptional<z.ZodString>;
854
+ sub: z.ZodOptional<z.ZodString>;
855
+ }, z.core.$strict>>;
856
+ }, z.core.$strict>, z.ZodObject<{
857
+ type: z.ZodLiteral<"roadmap">;
858
+ items: z.ZodArray<z.ZodObject<{
859
+ title: z.ZodString;
860
+ period: z.ZodOptional<z.ZodString>;
861
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
862
+ label: z.ZodString;
863
+ value: z.ZodString;
864
+ }, z.core.$strict>>>;
865
+ }, z.core.$strict>>;
866
+ }, z.core.$strict>, z.ZodObject<{
867
+ type: z.ZodLiteral<"matrix">;
868
+ x_title: z.ZodOptional<z.ZodString>;
869
+ y_title: z.ZodOptional<z.ZodString>;
870
+ cols: z.ZodNumber;
871
+ items: z.ZodArray<z.ZodObject<{
872
+ title: z.ZodString;
873
+ tag: z.ZodOptional<z.ZodString>;
874
+ tone: z.ZodOptional<z.ZodEnum<{
875
+ info: "info";
876
+ neutral: "neutral";
877
+ accent: "accent";
878
+ }>>;
879
+ }, z.core.$strict>>;
880
+ }, z.core.$strict>, z.ZodObject<{
881
+ type: z.ZodLiteral<"insight_panel">;
882
+ title: z.ZodString;
883
+ rows: z.ZodArray<z.ZodObject<{
884
+ label: z.ZodString;
885
+ text: z.ZodString;
886
+ }, z.core.$strict>>;
887
+ footnote: z.ZodOptional<z.ZodString>;
888
+ }, z.core.$strict>, z.ZodObject<{
889
+ type: z.ZodLiteral<"verdict_banner">;
890
+ text: z.ZodString;
891
+ tone: z.ZodEnum<{
892
+ positive: "positive";
893
+ warning: "warning";
894
+ neutral: "neutral";
895
+ }>;
896
+ icon: z.ZodOptional<z.ZodEnum<{
897
+ [x: string]: string;
898
+ }>>;
899
+ }, z.core.$strict>, z.ZodObject<{
900
+ type: z.ZodLiteral<"citation">;
901
+ sources: z.ZodArray<z.ZodObject<{
902
+ label: z.ZodString;
903
+ url: z.ZodOptional<z.ZodString>;
904
+ ref: z.ZodOptional<z.ZodString>;
905
+ }, z.core.$strict>>;
906
+ }, z.core.$strict>, z.ZodObject<{
907
+ type: z.ZodLiteral<"image">;
908
+ asset_id: z.ZodString;
909
+ caption: z.ZodOptional<z.ZodString>;
910
+ fit: z.ZodDefault<z.ZodEnum<{
911
+ contain: "contain";
912
+ cover: "cover";
913
+ }>>;
914
+ }, z.core.$strict>, z.ZodObject<{
915
+ type: z.ZodLiteral<"image_grid">;
916
+ items: z.ZodArray<z.ZodObject<{
917
+ asset_id: z.ZodString;
918
+ caption: z.ZodOptional<z.ZodString>;
919
+ }, z.core.$strict>>;
920
+ emphasis: z.ZodOptional<z.ZodEnum<{
921
+ none: "none";
922
+ first: "first";
923
+ }>>;
924
+ }, z.core.$strict>, z.ZodObject<{
925
+ type: z.ZodLiteral<"image_compare">;
926
+ left: z.ZodObject<{
927
+ asset_id: z.ZodString;
928
+ label: z.ZodString;
929
+ }, z.core.$strict>;
930
+ right: z.ZodObject<{
931
+ asset_id: z.ZodString;
932
+ label: z.ZodString;
933
+ }, z.core.$strict>;
934
+ style: z.ZodOptional<z.ZodEnum<{
935
+ vs: "vs";
936
+ before_after: "before_after";
937
+ }>>;
938
+ }, z.core.$strict>, z.ZodObject<{
939
+ type: z.ZodLiteral<"swot">;
940
+ strengths: z.ZodArray<z.ZodString>;
941
+ weaknesses: z.ZodArray<z.ZodString>;
942
+ opportunities: z.ZodArray<z.ZodString>;
943
+ threats: z.ZodArray<z.ZodString>;
944
+ labels: z.ZodOptional<z.ZodObject<{
945
+ strengths: z.ZodOptional<z.ZodString>;
946
+ weaknesses: z.ZodOptional<z.ZodString>;
947
+ opportunities: z.ZodOptional<z.ZodString>;
948
+ threats: z.ZodOptional<z.ZodString>;
949
+ }, z.core.$strict>>;
950
+ }, z.core.$strict>, z.ZodObject<{
951
+ type: z.ZodLiteral<"bmc">;
952
+ key_partners: z.ZodArray<z.ZodString>;
953
+ key_activities: z.ZodArray<z.ZodString>;
954
+ key_resources: z.ZodArray<z.ZodString>;
955
+ value_propositions: z.ZodArray<z.ZodString>;
956
+ customer_relationships: z.ZodArray<z.ZodString>;
957
+ channels: z.ZodArray<z.ZodString>;
958
+ customer_segments: z.ZodArray<z.ZodString>;
959
+ cost_structure: z.ZodArray<z.ZodString>;
960
+ revenue_streams: z.ZodArray<z.ZodString>;
961
+ }, z.core.$strict>, z.ZodObject<{
962
+ type: z.ZodLiteral<"waterfall">;
963
+ items: z.ZodArray<z.ZodObject<{
964
+ label: z.ZodString;
965
+ value: z.ZodNumber;
966
+ kind: z.ZodOptional<z.ZodEnum<{
967
+ delta: "delta";
968
+ total: "total";
969
+ }>>;
970
+ }, z.core.$strict>>;
971
+ unit: z.ZodOptional<z.ZodString>;
972
+ }, z.core.$strict>, z.ZodObject<{
973
+ type: z.ZodLiteral<"gantt">;
974
+ items: z.ZodArray<z.ZodObject<{
975
+ label: z.ZodString;
976
+ start: z.ZodNumber;
977
+ end: z.ZodNumber;
978
+ }, z.core.$strict>>;
979
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
980
+ }, z.core.$strict>, z.ZodObject<{
981
+ type: z.ZodLiteral<"pest">;
982
+ political: z.ZodObject<{
983
+ title: z.ZodOptional<z.ZodString>;
984
+ items: z.ZodArray<z.ZodString>;
985
+ }, z.core.$strict>;
986
+ economic: z.ZodObject<{
987
+ title: z.ZodOptional<z.ZodString>;
988
+ items: z.ZodArray<z.ZodString>;
989
+ }, z.core.$strict>;
990
+ social: z.ZodObject<{
991
+ title: z.ZodOptional<z.ZodString>;
992
+ items: z.ZodArray<z.ZodString>;
993
+ }, z.core.$strict>;
994
+ technological: z.ZodObject<{
995
+ title: z.ZodOptional<z.ZodString>;
996
+ items: z.ZodArray<z.ZodString>;
997
+ }, z.core.$strict>;
998
+ }, z.core.$strict>, z.ZodObject<{
999
+ type: z.ZodLiteral<"five_forces">;
1000
+ rivalry: z.ZodObject<{
1001
+ label: z.ZodOptional<z.ZodString>;
1002
+ intensity: z.ZodOptional<z.ZodEnum<{
1003
+ low: "low";
1004
+ medium: "medium";
1005
+ high: "high";
1006
+ }>>;
1007
+ items: z.ZodArray<z.ZodString>;
1008
+ }, z.core.$strict>;
1009
+ new_entrants: z.ZodObject<{
1010
+ label: z.ZodOptional<z.ZodString>;
1011
+ intensity: z.ZodOptional<z.ZodEnum<{
1012
+ low: "low";
1013
+ medium: "medium";
1014
+ high: "high";
1015
+ }>>;
1016
+ items: z.ZodArray<z.ZodString>;
1017
+ }, z.core.$strict>;
1018
+ supplier_power: z.ZodObject<{
1019
+ label: z.ZodOptional<z.ZodString>;
1020
+ intensity: z.ZodOptional<z.ZodEnum<{
1021
+ low: "low";
1022
+ medium: "medium";
1023
+ high: "high";
1024
+ }>>;
1025
+ items: z.ZodArray<z.ZodString>;
1026
+ }, z.core.$strict>;
1027
+ buyer_power: z.ZodObject<{
1028
+ label: z.ZodOptional<z.ZodString>;
1029
+ intensity: z.ZodOptional<z.ZodEnum<{
1030
+ low: "low";
1031
+ medium: "medium";
1032
+ high: "high";
1033
+ }>>;
1034
+ items: z.ZodArray<z.ZodString>;
1035
+ }, z.core.$strict>;
1036
+ substitutes: z.ZodObject<{
1037
+ label: z.ZodOptional<z.ZodString>;
1038
+ intensity: z.ZodOptional<z.ZodEnum<{
1039
+ low: "low";
1040
+ medium: "medium";
1041
+ high: "high";
1042
+ }>>;
1043
+ items: z.ZodArray<z.ZodString>;
1044
+ }, z.core.$strict>;
1045
+ }, z.core.$strict>, z.ZodObject<{
1046
+ type: z.ZodLiteral<"heatmap">;
1047
+ x_labels: z.ZodArray<z.ZodString>;
1048
+ y_labels: z.ZodArray<z.ZodString>;
1049
+ values: z.ZodArray<z.ZodArray<z.ZodNumber>>;
1050
+ domain: z.ZodOptional<z.ZodObject<{
1051
+ min: z.ZodNumber;
1052
+ max: z.ZodNumber;
1053
+ }, z.core.$strict>>;
1054
+ show_values: z.ZodOptional<z.ZodBoolean>;
1055
+ x_title: z.ZodOptional<z.ZodString>;
1056
+ y_title: z.ZodOptional<z.ZodString>;
1057
+ }, z.core.$strict>, z.ZodObject<{
1058
+ type: z.ZodLiteral<"sankey">;
1059
+ nodes: z.ZodArray<z.ZodObject<{
1060
+ id: z.ZodString;
1061
+ label: z.ZodString;
1062
+ }, z.core.$strict>>;
1063
+ links: z.ZodArray<z.ZodObject<{
1064
+ from: z.ZodString;
1065
+ to: z.ZodString;
1066
+ value: z.ZodNumber;
1067
+ }, z.core.$strict>>;
1068
+ }, z.core.$strict>, z.ZodObject<{
1069
+ type: z.ZodLiteral<"data_table">;
1070
+ columns: z.ZodArray<z.ZodObject<{
1071
+ key: z.ZodString;
1072
+ label: z.ZodString;
1073
+ align: z.ZodOptional<z.ZodEnum<{
1074
+ left: "left";
1075
+ right: "right";
1076
+ center: "center";
1077
+ }>>;
1078
+ }, z.core.$strict>>;
1079
+ rows: z.ZodArray<z.ZodObject<{
1080
+ cells: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
1081
+ emphasis: z.ZodOptional<z.ZodEnum<{
1082
+ highlight: "highlight";
1083
+ total: "total";
1084
+ }>>;
1085
+ }, z.core.$strict>>;
1086
+ source: z.ZodOptional<z.ZodString>;
1087
+ }, z.core.$strict>, z.ZodObject<{
1088
+ type: z.ZodLiteral<"device_mockup">;
1089
+ device: z.ZodEnum<{
1090
+ phone: "phone";
1091
+ browser: "browser";
1092
+ }>;
1093
+ asset_id: z.ZodString;
1094
+ caption: z.ZodOptional<z.ZodString>;
1095
+ url: z.ZodOptional<z.ZodString>;
1096
+ }, z.core.$strict>, z.ZodObject<{
1097
+ type: z.ZodLiteral<"cycle">;
1098
+ title: z.ZodOptional<z.ZodString>;
1099
+ items: z.ZodArray<z.ZodObject<{
1100
+ label: z.ZodString;
1101
+ description: z.ZodOptional<z.ZodString>;
1102
+ }, z.core.$strict>>;
1103
+ }, z.core.$strict>, z.ZodObject<{
1104
+ type: z.ZodLiteral<"people_cards">;
1105
+ title: z.ZodOptional<z.ZodString>;
1106
+ people: z.ZodArray<z.ZodObject<{
1107
+ name: z.ZodString;
1108
+ role: z.ZodOptional<z.ZodString>;
1109
+ org: z.ZodOptional<z.ZodString>;
1110
+ }, z.core.$strict>>;
1111
+ }, z.core.$strict>, z.ZodObject<{
1112
+ type: z.ZodLiteral<"tag_row">;
1113
+ title: z.ZodOptional<z.ZodString>;
1114
+ items: z.ZodArray<z.ZodString>;
1115
+ emphasis: z.ZodOptional<z.ZodEnum<{
1116
+ none: "none";
1117
+ first: "first";
1118
+ }>>;
1119
+ }, z.core.$strict>], "type">>>;
1120
+ background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1121
+ kind: z.ZodLiteral<"color">;
1122
+ value: z.ZodString;
1123
+ }, z.core.$strict>, z.ZodObject<{
1124
+ kind: z.ZodLiteral<"gradient">;
1125
+ from: z.ZodString;
1126
+ to: z.ZodString;
1127
+ direction: z.ZodOptional<z.ZodEnum<{
1128
+ tb: "tb";
1129
+ lr: "lr";
1130
+ diagonal: "diagonal";
1131
+ }>>;
1132
+ }, z.core.$strict>, z.ZodObject<{
1133
+ kind: z.ZodLiteral<"asset">;
1134
+ asset_id: z.ZodString;
1135
+ overlay: z.ZodOptional<z.ZodObject<{
1136
+ color: z.ZodString;
1137
+ opacity: z.ZodNumber;
1138
+ }, z.core.$strict>>;
1139
+ fit: z.ZodOptional<z.ZodEnum<{
1140
+ contain: "contain";
1141
+ cover: "cover";
1142
+ }>>;
1143
+ }, z.core.$strict>], "kind">>;
1144
+ decor: z.ZodOptional<z.ZodObject<{
1145
+ kind: z.ZodEnum<{
1146
+ big_number: "big_number";
1147
+ corner_tag: "corner_tag";
1148
+ rule_line: "rule_line";
1149
+ quote_marks: "quote_marks";
1150
+ geo_dots: "geo_dots";
1151
+ }>;
1152
+ intensity: z.ZodOptional<z.ZodEnum<{
1153
+ subtle: "subtle";
1154
+ normal: "normal";
1155
+ }>>;
1156
+ text: z.ZodOptional<z.ZodString>;
1157
+ }, z.core.$strict>>;
1158
+ image_side: z.ZodOptional<z.ZodEnum<{
1159
+ left: "left";
1160
+ right: "right";
1161
+ }>>;
1162
+ footnote: z.ZodOptional<z.ZodString>;
1163
+ notes: z.ZodOptional<z.ZodString>;
1164
+ }, z.core.$strict>;
1165
+ declare const PptxIRSchema: z.ZodObject<{
1166
+ version: z.ZodDefault<z.ZodLiteral<"4">>;
1167
+ filename: z.ZodDefault<z.ZodString>;
1168
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1169
+ theme: z.ZodDefault<z.ZodObject<{
1170
+ id: z.ZodDefault<z.ZodString>;
1171
+ style: z.ZodOptional<z.ZodObject<{
1172
+ colors: z.ZodOptional<z.ZodObject<{
1173
+ bg: z.ZodOptional<z.ZodString>;
1174
+ surface: z.ZodOptional<z.ZodString>;
1175
+ panel: z.ZodOptional<z.ZodString>;
1176
+ primary: z.ZodOptional<z.ZodString>;
1177
+ accent: z.ZodOptional<z.ZodString>;
1178
+ text: z.ZodOptional<z.ZodString>;
1179
+ muted: z.ZodOptional<z.ZodString>;
1180
+ border: z.ZodOptional<z.ZodString>;
1181
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
1182
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
1183
+ cardStroke: z.ZodOptional<z.ZodString>;
1184
+ }, z.core.$strict>>;
1185
+ fonts: z.ZodOptional<z.ZodObject<{
1186
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
1187
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
1188
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
1189
+ }, z.core.$strict>>;
1190
+ shape: z.ZodOptional<z.ZodObject<{
1191
+ radius: z.ZodOptional<z.ZodNumber>;
1192
+ gapScale: z.ZodOptional<z.ZodNumber>;
1193
+ typeScale: z.ZodOptional<z.ZodNumber>;
1194
+ }, z.core.$strict>>;
1195
+ }, z.core.$strict>>;
1196
+ brand: z.ZodOptional<z.ZodObject<{
1197
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
1198
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
1199
+ suppressFooterMeta: z.ZodOptional<z.ZodBoolean>;
1200
+ }, z.core.$strict>>;
1201
+ }, z.core.$strict>>;
1202
+ meta: z.ZodDefault<z.ZodObject<{
1203
+ organization: z.ZodOptional<z.ZodString>;
1204
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
1205
+ name: z.ZodString;
1206
+ role: z.ZodOptional<z.ZodString>;
1207
+ org: z.ZodOptional<z.ZodString>;
1208
+ }, z.core.$strict>>>;
1209
+ date: z.ZodOptional<z.ZodString>;
1210
+ version: z.ZodOptional<z.ZodString>;
1211
+ confidentiality: z.ZodOptional<z.ZodEnum<{
1212
+ public: "public";
1213
+ internal: "internal";
1214
+ confidential: "confidential";
1215
+ restricted: "restricted";
1216
+ }>>;
1217
+ contact: z.ZodOptional<z.ZodObject<{
1218
+ name: z.ZodOptional<z.ZodString>;
1219
+ email: z.ZodOptional<z.ZodString>;
1220
+ phone: z.ZodOptional<z.ZodString>;
1221
+ website: z.ZodOptional<z.ZodString>;
1222
+ }, z.core.$strict>>;
1223
+ copyright: z.ZodOptional<z.ZodString>;
1224
+ animation: z.ZodOptional<z.ZodObject<{
1225
+ transition: z.ZodOptional<z.ZodEnum<{
1226
+ push: "push";
1227
+ none: "none";
1228
+ fade: "fade";
1229
+ wipe: "wipe";
1230
+ }>>;
1231
+ elements: z.ZodOptional<z.ZodEnum<{
1232
+ none: "none";
1233
+ auto: "auto";
1234
+ }>>;
1235
+ }, z.core.$strict>>;
1236
+ }, z.core.$strict>>;
1237
+ assets: z.ZodDefault<z.ZodObject<{
1238
+ images: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1239
+ src: z.ZodString;
1240
+ alt: z.ZodOptional<z.ZodString>;
1241
+ error: z.ZodOptional<z.ZodString>;
1242
+ }, z.core.$strict>>>;
1243
+ }, z.core.$strict>>;
1244
+ brand: z.ZodOptional<z.ZodObject<{
1245
+ logo_asset_id: z.ZodOptional<z.ZodString>;
1246
+ position: z.ZodOptional<z.ZodEnum<{
1247
+ tl: "tl";
1248
+ tr: "tr";
1249
+ bl: "bl";
1250
+ br: "br";
1251
+ }>>;
1252
+ }, z.core.$strict>>;
1253
+ branding: z.ZodOptional<z.ZodEnum<{
1254
+ full: "full";
1255
+ "cover-only": "cover-only";
1256
+ minimal: "minimal";
1257
+ }>>;
1258
+ seed: z.ZodOptional<z.ZodNumber>;
1259
+ slides: z.ZodArray<z.ZodObject<{
1260
+ type: z.ZodDefault<z.ZodEnum<{
1261
+ content: "content";
1262
+ cover: "cover";
1263
+ chapter: "chapter";
1264
+ ending: "ending";
1265
+ }>>;
1266
+ id: z.ZodOptional<z.ZodString>;
1267
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
1268
+ layout: z.ZodOptional<z.ZodString>;
1269
+ beat: z.ZodOptional<z.ZodEnum<{
1270
+ dense: "dense";
1271
+ anchor: "anchor";
1272
+ breathing: "breathing";
1273
+ }>>;
1274
+ arrangement: z.ZodOptional<z.ZodEnum<{
1275
+ code: "code";
1276
+ quote: "quote";
1277
+ single: "single";
1278
+ two_column: "two_column";
1279
+ kpi_focus: "kpi_focus";
1280
+ image_focus: "image_focus";
1281
+ big_number: "big_number";
1282
+ assertion_evidence: "assertion_evidence";
1283
+ aside: "aside";
1284
+ }>>;
1285
+ heading: z.ZodOptional<z.ZodString>;
1286
+ subheading: z.ZodOptional<z.ZodString>;
1287
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1288
+ type: z.ZodLiteral<"bullets">;
1289
+ items: z.ZodArray<z.ZodString>;
1290
+ style: z.ZodOptional<z.ZodEnum<{
1291
+ default: "default";
1292
+ checklist: "checklist";
1293
+ numbered: "numbered";
1294
+ plain: "plain";
1295
+ divided: "divided";
1296
+ }>>;
1297
+ }, z.core.$strict>, z.ZodObject<{
1298
+ type: z.ZodLiteral<"paragraph">;
1299
+ text: z.ZodString;
1300
+ }, z.core.$strict>, z.ZodObject<{
1301
+ type: z.ZodLiteral<"quote">;
1302
+ text: z.ZodString;
1303
+ attribution: z.ZodOptional<z.ZodString>;
1304
+ }, z.core.$strict>, z.ZodObject<{
1305
+ type: z.ZodLiteral<"callout">;
1306
+ variant: z.ZodEnum<{
1307
+ info: "info";
1308
+ warn: "warn";
1309
+ tip: "tip";
1310
+ }>;
1311
+ text: z.ZodString;
1312
+ icon: z.ZodOptional<z.ZodEnum<{
1313
+ [x: string]: string;
1314
+ }>>;
1315
+ }, z.core.$strict>, z.ZodObject<{
1316
+ type: z.ZodLiteral<"code">;
1317
+ language: z.ZodString;
1318
+ code: z.ZodString;
1319
+ }, z.core.$strict>, z.ZodObject<{
1320
+ type: z.ZodLiteral<"kpi_cards">;
1321
+ items: z.ZodArray<z.ZodObject<{
1322
+ value: z.ZodString;
1323
+ unit: z.ZodOptional<z.ZodString>;
1324
+ label: z.ZodString;
1325
+ delta: z.ZodOptional<z.ZodEnum<{
1326
+ flat: "flat";
1327
+ up: "up";
1328
+ down: "down";
1329
+ }>>;
1330
+ icon: z.ZodOptional<z.ZodEnum<{
1331
+ [x: string]: string;
1332
+ }>>;
1333
+ source: z.ZodOptional<z.ZodString>;
1334
+ }, z.core.$strict>>;
1335
+ }, z.core.$strict>, z.ZodObject<{
1336
+ type: z.ZodLiteral<"chart">;
1337
+ chart_type: z.ZodEnum<{
1338
+ line: "line";
1339
+ donut: "donut";
1340
+ dumbbell: "dumbbell";
1341
+ funnel: "funnel";
1342
+ gauge: "gauge";
1343
+ bar: "bar";
1344
+ pie: "pie";
1345
+ scatter: "scatter";
1346
+ area: "area";
1347
+ }>;
1348
+ direction: z.ZodOptional<z.ZodEnum<{
1349
+ horizontal: "horizontal";
1350
+ vertical: "vertical";
1351
+ }>>;
1352
+ style: z.ZodOptional<z.ZodEnum<{
1353
+ donut: "donut";
1354
+ }>>;
1355
+ center_total: z.ZodOptional<z.ZodBoolean>;
1356
+ gauge: z.ZodOptional<z.ZodObject<{
1357
+ min: z.ZodOptional<z.ZodNumber>;
1358
+ max: z.ZodOptional<z.ZodNumber>;
1359
+ }, z.core.$strict>>;
1360
+ axes: z.ZodOptional<z.ZodObject<{
1361
+ x_title: z.ZodOptional<z.ZodString>;
1362
+ y_title: z.ZodOptional<z.ZodString>;
1363
+ show_grid: z.ZodOptional<z.ZodBoolean>;
1364
+ }, z.core.$strict>>;
1365
+ series: z.ZodArray<z.ZodObject<{
1366
+ name: z.ZodString;
1367
+ data: z.ZodArray<z.ZodObject<{
1368
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1369
+ y: z.ZodNumber;
1370
+ size: z.ZodOptional<z.ZodNumber>;
1371
+ }, z.core.$strict>>;
1372
+ }, z.core.$strict>>;
1373
+ }, z.core.$strict>, z.ZodObject<{
1374
+ type: z.ZodLiteral<"flowchart">;
1375
+ nodes: z.ZodArray<z.ZodObject<{
1376
+ id: z.ZodString;
1377
+ label: z.ZodString;
1378
+ kind: z.ZodOptional<z.ZodEnum<{
1379
+ rect: "rect";
1380
+ diamond: "diamond";
1381
+ round: "round";
1382
+ }>>;
1383
+ }, z.core.$strict>>;
1384
+ edges: z.ZodArray<z.ZodObject<{
1385
+ from: z.ZodString;
1386
+ to: z.ZodString;
1387
+ label: z.ZodOptional<z.ZodString>;
1388
+ }, z.core.$strict>>;
1389
+ direction: z.ZodOptional<z.ZodEnum<{
1390
+ TB: "TB";
1391
+ TD: "TD";
1392
+ BT: "BT";
1393
+ LR: "LR";
1394
+ RL: "RL";
1395
+ }>>;
1396
+ }, z.core.$strict>, z.ZodObject<{
1397
+ type: z.ZodLiteral<"architecture">;
1398
+ layers: z.ZodArray<z.ZodObject<{
1399
+ title: z.ZodString;
1400
+ items: z.ZodArray<z.ZodString>;
1401
+ }, z.core.$strict>>;
1402
+ direction: z.ZodOptional<z.ZodEnum<{
1403
+ top_down: "top_down";
1404
+ bottom_up: "bottom_up";
1405
+ }>>;
1406
+ }, z.core.$strict>, z.ZodObject<{
1407
+ type: z.ZodLiteral<"timeline">;
1408
+ layout: z.ZodOptional<z.ZodEnum<{
1409
+ horizontal: "horizontal";
1410
+ vertical: "vertical";
1411
+ }>>;
1412
+ milestones: z.ZodArray<z.ZodObject<{
1413
+ date: z.ZodString;
1414
+ title: z.ZodString;
1415
+ desc: z.ZodOptional<z.ZodString>;
1416
+ highlight: z.ZodOptional<z.ZodBoolean>;
1417
+ }, z.core.$strict>>;
1418
+ }, z.core.$strict>, z.ZodObject<{
1419
+ type: z.ZodLiteral<"comparison">;
1420
+ columns: z.ZodArray<z.ZodString>;
1421
+ rows: z.ZodArray<z.ZodObject<{
1422
+ label: z.ZodString;
1423
+ cells: z.ZodArray<z.ZodString>;
1424
+ }, z.core.$strict>>;
1425
+ }, z.core.$strict>, z.ZodObject<{
1426
+ type: z.ZodLiteral<"icon_cards">;
1427
+ items: z.ZodArray<z.ZodObject<{
1428
+ icon: z.ZodEnum<{
1429
+ [x: string]: string;
1430
+ }>;
1431
+ title: z.ZodString;
1432
+ text: z.ZodString;
1433
+ }, z.core.$strict>>;
1434
+ }, z.core.$strict>, z.ZodObject<{
1435
+ type: z.ZodLiteral<"row_cards">;
1436
+ items: z.ZodArray<z.ZodObject<{
1437
+ icon: z.ZodOptional<z.ZodEnum<{
1438
+ [x: string]: string;
1439
+ }>>;
1440
+ title: z.ZodString;
1441
+ text: z.ZodOptional<z.ZodString>;
1442
+ sub: z.ZodOptional<z.ZodString>;
1443
+ highlight: z.ZodOptional<z.ZodBoolean>;
1444
+ }, z.core.$strict>>;
1445
+ }, z.core.$strict>, z.ZodObject<{
1446
+ type: z.ZodLiteral<"steps">;
1447
+ items: z.ZodArray<z.ZodObject<{
1448
+ title: z.ZodString;
1449
+ text: z.ZodString;
1450
+ }, z.core.$strict>>;
1451
+ }, z.core.$strict>, z.ZodObject<{
1452
+ type: z.ZodLiteral<"rings">;
1453
+ items: z.ZodArray<z.ZodObject<{
1454
+ label: z.ZodString;
1455
+ desc: z.ZodOptional<z.ZodString>;
1456
+ }, z.core.$strict>>;
1457
+ }, z.core.$strict>, z.ZodObject<{
1458
+ type: z.ZodLiteral<"numbered_cards">;
1459
+ items: z.ZodArray<z.ZodObject<{
1460
+ title: z.ZodString;
1461
+ text: z.ZodOptional<z.ZodString>;
1462
+ sub: z.ZodOptional<z.ZodString>;
1463
+ }, z.core.$strict>>;
1464
+ }, z.core.$strict>, z.ZodObject<{
1465
+ type: z.ZodLiteral<"roadmap">;
1466
+ items: z.ZodArray<z.ZodObject<{
1467
+ title: z.ZodString;
1468
+ period: z.ZodOptional<z.ZodString>;
1469
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
1470
+ label: z.ZodString;
1471
+ value: z.ZodString;
1472
+ }, z.core.$strict>>>;
1473
+ }, z.core.$strict>>;
1474
+ }, z.core.$strict>, z.ZodObject<{
1475
+ type: z.ZodLiteral<"matrix">;
1476
+ x_title: z.ZodOptional<z.ZodString>;
1477
+ y_title: z.ZodOptional<z.ZodString>;
1478
+ cols: z.ZodNumber;
1479
+ items: z.ZodArray<z.ZodObject<{
1480
+ title: z.ZodString;
1481
+ tag: z.ZodOptional<z.ZodString>;
1482
+ tone: z.ZodOptional<z.ZodEnum<{
1483
+ info: "info";
1484
+ neutral: "neutral";
1485
+ accent: "accent";
1486
+ }>>;
1487
+ }, z.core.$strict>>;
1488
+ }, z.core.$strict>, z.ZodObject<{
1489
+ type: z.ZodLiteral<"insight_panel">;
1490
+ title: z.ZodString;
1491
+ rows: z.ZodArray<z.ZodObject<{
1492
+ label: z.ZodString;
1493
+ text: z.ZodString;
1494
+ }, z.core.$strict>>;
1495
+ footnote: z.ZodOptional<z.ZodString>;
1496
+ }, z.core.$strict>, z.ZodObject<{
1497
+ type: z.ZodLiteral<"verdict_banner">;
1498
+ text: z.ZodString;
1499
+ tone: z.ZodEnum<{
1500
+ positive: "positive";
1501
+ warning: "warning";
1502
+ neutral: "neutral";
1503
+ }>;
1504
+ icon: z.ZodOptional<z.ZodEnum<{
1505
+ [x: string]: string;
1506
+ }>>;
1507
+ }, z.core.$strict>, z.ZodObject<{
1508
+ type: z.ZodLiteral<"citation">;
1509
+ sources: z.ZodArray<z.ZodObject<{
1510
+ label: z.ZodString;
1511
+ url: z.ZodOptional<z.ZodString>;
1512
+ ref: z.ZodOptional<z.ZodString>;
1513
+ }, z.core.$strict>>;
1514
+ }, z.core.$strict>, z.ZodObject<{
1515
+ type: z.ZodLiteral<"image">;
1516
+ asset_id: z.ZodString;
1517
+ caption: z.ZodOptional<z.ZodString>;
1518
+ fit: z.ZodDefault<z.ZodEnum<{
1519
+ contain: "contain";
1520
+ cover: "cover";
1521
+ }>>;
1522
+ }, z.core.$strict>, z.ZodObject<{
1523
+ type: z.ZodLiteral<"image_grid">;
1524
+ items: z.ZodArray<z.ZodObject<{
1525
+ asset_id: z.ZodString;
1526
+ caption: z.ZodOptional<z.ZodString>;
1527
+ }, z.core.$strict>>;
1528
+ emphasis: z.ZodOptional<z.ZodEnum<{
1529
+ none: "none";
1530
+ first: "first";
1531
+ }>>;
1532
+ }, z.core.$strict>, z.ZodObject<{
1533
+ type: z.ZodLiteral<"image_compare">;
1534
+ left: z.ZodObject<{
1535
+ asset_id: z.ZodString;
1536
+ label: z.ZodString;
1537
+ }, z.core.$strict>;
1538
+ right: z.ZodObject<{
1539
+ asset_id: z.ZodString;
1540
+ label: z.ZodString;
1541
+ }, z.core.$strict>;
1542
+ style: z.ZodOptional<z.ZodEnum<{
1543
+ vs: "vs";
1544
+ before_after: "before_after";
1545
+ }>>;
1546
+ }, z.core.$strict>, z.ZodObject<{
1547
+ type: z.ZodLiteral<"swot">;
1548
+ strengths: z.ZodArray<z.ZodString>;
1549
+ weaknesses: z.ZodArray<z.ZodString>;
1550
+ opportunities: z.ZodArray<z.ZodString>;
1551
+ threats: z.ZodArray<z.ZodString>;
1552
+ labels: z.ZodOptional<z.ZodObject<{
1553
+ strengths: z.ZodOptional<z.ZodString>;
1554
+ weaknesses: z.ZodOptional<z.ZodString>;
1555
+ opportunities: z.ZodOptional<z.ZodString>;
1556
+ threats: z.ZodOptional<z.ZodString>;
1557
+ }, z.core.$strict>>;
1558
+ }, z.core.$strict>, z.ZodObject<{
1559
+ type: z.ZodLiteral<"bmc">;
1560
+ key_partners: z.ZodArray<z.ZodString>;
1561
+ key_activities: z.ZodArray<z.ZodString>;
1562
+ key_resources: z.ZodArray<z.ZodString>;
1563
+ value_propositions: z.ZodArray<z.ZodString>;
1564
+ customer_relationships: z.ZodArray<z.ZodString>;
1565
+ channels: z.ZodArray<z.ZodString>;
1566
+ customer_segments: z.ZodArray<z.ZodString>;
1567
+ cost_structure: z.ZodArray<z.ZodString>;
1568
+ revenue_streams: z.ZodArray<z.ZodString>;
1569
+ }, z.core.$strict>, z.ZodObject<{
1570
+ type: z.ZodLiteral<"waterfall">;
1571
+ items: z.ZodArray<z.ZodObject<{
1572
+ label: z.ZodString;
1573
+ value: z.ZodNumber;
1574
+ kind: z.ZodOptional<z.ZodEnum<{
1575
+ delta: "delta";
1576
+ total: "total";
1577
+ }>>;
1578
+ }, z.core.$strict>>;
1579
+ unit: z.ZodOptional<z.ZodString>;
1580
+ }, z.core.$strict>, z.ZodObject<{
1581
+ type: z.ZodLiteral<"gantt">;
1582
+ items: z.ZodArray<z.ZodObject<{
1583
+ label: z.ZodString;
1584
+ start: z.ZodNumber;
1585
+ end: z.ZodNumber;
1586
+ }, z.core.$strict>>;
1587
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1588
+ }, z.core.$strict>, z.ZodObject<{
1589
+ type: z.ZodLiteral<"pest">;
1590
+ political: z.ZodObject<{
1591
+ title: z.ZodOptional<z.ZodString>;
1592
+ items: z.ZodArray<z.ZodString>;
1593
+ }, z.core.$strict>;
1594
+ economic: z.ZodObject<{
1595
+ title: z.ZodOptional<z.ZodString>;
1596
+ items: z.ZodArray<z.ZodString>;
1597
+ }, z.core.$strict>;
1598
+ social: z.ZodObject<{
1599
+ title: z.ZodOptional<z.ZodString>;
1600
+ items: z.ZodArray<z.ZodString>;
1601
+ }, z.core.$strict>;
1602
+ technological: z.ZodObject<{
1603
+ title: z.ZodOptional<z.ZodString>;
1604
+ items: z.ZodArray<z.ZodString>;
1605
+ }, z.core.$strict>;
1606
+ }, z.core.$strict>, z.ZodObject<{
1607
+ type: z.ZodLiteral<"five_forces">;
1608
+ rivalry: z.ZodObject<{
1609
+ label: z.ZodOptional<z.ZodString>;
1610
+ intensity: z.ZodOptional<z.ZodEnum<{
1611
+ low: "low";
1612
+ medium: "medium";
1613
+ high: "high";
1614
+ }>>;
1615
+ items: z.ZodArray<z.ZodString>;
1616
+ }, z.core.$strict>;
1617
+ new_entrants: z.ZodObject<{
1618
+ label: z.ZodOptional<z.ZodString>;
1619
+ intensity: z.ZodOptional<z.ZodEnum<{
1620
+ low: "low";
1621
+ medium: "medium";
1622
+ high: "high";
1623
+ }>>;
1624
+ items: z.ZodArray<z.ZodString>;
1625
+ }, z.core.$strict>;
1626
+ supplier_power: z.ZodObject<{
1627
+ label: z.ZodOptional<z.ZodString>;
1628
+ intensity: z.ZodOptional<z.ZodEnum<{
1629
+ low: "low";
1630
+ medium: "medium";
1631
+ high: "high";
1632
+ }>>;
1633
+ items: z.ZodArray<z.ZodString>;
1634
+ }, z.core.$strict>;
1635
+ buyer_power: z.ZodObject<{
1636
+ label: z.ZodOptional<z.ZodString>;
1637
+ intensity: z.ZodOptional<z.ZodEnum<{
1638
+ low: "low";
1639
+ medium: "medium";
1640
+ high: "high";
1641
+ }>>;
1642
+ items: z.ZodArray<z.ZodString>;
1643
+ }, z.core.$strict>;
1644
+ substitutes: z.ZodObject<{
1645
+ label: z.ZodOptional<z.ZodString>;
1646
+ intensity: z.ZodOptional<z.ZodEnum<{
1647
+ low: "low";
1648
+ medium: "medium";
1649
+ high: "high";
1650
+ }>>;
1651
+ items: z.ZodArray<z.ZodString>;
1652
+ }, z.core.$strict>;
1653
+ }, z.core.$strict>, z.ZodObject<{
1654
+ type: z.ZodLiteral<"heatmap">;
1655
+ x_labels: z.ZodArray<z.ZodString>;
1656
+ y_labels: z.ZodArray<z.ZodString>;
1657
+ values: z.ZodArray<z.ZodArray<z.ZodNumber>>;
1658
+ domain: z.ZodOptional<z.ZodObject<{
1659
+ min: z.ZodNumber;
1660
+ max: z.ZodNumber;
1661
+ }, z.core.$strict>>;
1662
+ show_values: z.ZodOptional<z.ZodBoolean>;
1663
+ x_title: z.ZodOptional<z.ZodString>;
1664
+ y_title: z.ZodOptional<z.ZodString>;
1665
+ }, z.core.$strict>, z.ZodObject<{
1666
+ type: z.ZodLiteral<"sankey">;
1667
+ nodes: z.ZodArray<z.ZodObject<{
1668
+ id: z.ZodString;
1669
+ label: z.ZodString;
1670
+ }, z.core.$strict>>;
1671
+ links: z.ZodArray<z.ZodObject<{
1672
+ from: z.ZodString;
1673
+ to: z.ZodString;
1674
+ value: z.ZodNumber;
1675
+ }, z.core.$strict>>;
1676
+ }, z.core.$strict>, z.ZodObject<{
1677
+ type: z.ZodLiteral<"data_table">;
1678
+ columns: z.ZodArray<z.ZodObject<{
1679
+ key: z.ZodString;
1680
+ label: z.ZodString;
1681
+ align: z.ZodOptional<z.ZodEnum<{
1682
+ left: "left";
1683
+ right: "right";
1684
+ center: "center";
1685
+ }>>;
1686
+ }, z.core.$strict>>;
1687
+ rows: z.ZodArray<z.ZodObject<{
1688
+ cells: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
1689
+ emphasis: z.ZodOptional<z.ZodEnum<{
1690
+ highlight: "highlight";
1691
+ total: "total";
1692
+ }>>;
1693
+ }, z.core.$strict>>;
1694
+ source: z.ZodOptional<z.ZodString>;
1695
+ }, z.core.$strict>, z.ZodObject<{
1696
+ type: z.ZodLiteral<"device_mockup">;
1697
+ device: z.ZodEnum<{
1698
+ phone: "phone";
1699
+ browser: "browser";
1700
+ }>;
1701
+ asset_id: z.ZodString;
1702
+ caption: z.ZodOptional<z.ZodString>;
1703
+ url: z.ZodOptional<z.ZodString>;
1704
+ }, z.core.$strict>, z.ZodObject<{
1705
+ type: z.ZodLiteral<"cycle">;
1706
+ title: z.ZodOptional<z.ZodString>;
1707
+ items: z.ZodArray<z.ZodObject<{
1708
+ label: z.ZodString;
1709
+ description: z.ZodOptional<z.ZodString>;
1710
+ }, z.core.$strict>>;
1711
+ }, z.core.$strict>, z.ZodObject<{
1712
+ type: z.ZodLiteral<"people_cards">;
1713
+ title: z.ZodOptional<z.ZodString>;
1714
+ people: z.ZodArray<z.ZodObject<{
1715
+ name: z.ZodString;
1716
+ role: z.ZodOptional<z.ZodString>;
1717
+ org: z.ZodOptional<z.ZodString>;
1718
+ }, z.core.$strict>>;
1719
+ }, z.core.$strict>, z.ZodObject<{
1720
+ type: z.ZodLiteral<"tag_row">;
1721
+ title: z.ZodOptional<z.ZodString>;
1722
+ items: z.ZodArray<z.ZodString>;
1723
+ emphasis: z.ZodOptional<z.ZodEnum<{
1724
+ none: "none";
1725
+ first: "first";
1726
+ }>>;
1727
+ }, z.core.$strict>], "type">>>;
1728
+ background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1729
+ kind: z.ZodLiteral<"color">;
1730
+ value: z.ZodString;
1731
+ }, z.core.$strict>, z.ZodObject<{
1732
+ kind: z.ZodLiteral<"gradient">;
1733
+ from: z.ZodString;
1734
+ to: z.ZodString;
1735
+ direction: z.ZodOptional<z.ZodEnum<{
1736
+ tb: "tb";
1737
+ lr: "lr";
1738
+ diagonal: "diagonal";
1739
+ }>>;
1740
+ }, z.core.$strict>, z.ZodObject<{
1741
+ kind: z.ZodLiteral<"asset">;
1742
+ asset_id: z.ZodString;
1743
+ overlay: z.ZodOptional<z.ZodObject<{
1744
+ color: z.ZodString;
1745
+ opacity: z.ZodNumber;
1746
+ }, z.core.$strict>>;
1747
+ fit: z.ZodOptional<z.ZodEnum<{
1748
+ contain: "contain";
1749
+ cover: "cover";
1750
+ }>>;
1751
+ }, z.core.$strict>], "kind">>;
1752
+ decor: z.ZodOptional<z.ZodObject<{
1753
+ kind: z.ZodEnum<{
1754
+ big_number: "big_number";
1755
+ corner_tag: "corner_tag";
1756
+ rule_line: "rule_line";
1757
+ quote_marks: "quote_marks";
1758
+ geo_dots: "geo_dots";
1759
+ }>;
1760
+ intensity: z.ZodOptional<z.ZodEnum<{
1761
+ subtle: "subtle";
1762
+ normal: "normal";
1763
+ }>>;
1764
+ text: z.ZodOptional<z.ZodString>;
1765
+ }, z.core.$strict>>;
1766
+ image_side: z.ZodOptional<z.ZodEnum<{
1767
+ left: "left";
1768
+ right: "right";
1769
+ }>>;
1770
+ footnote: z.ZodOptional<z.ZodString>;
1771
+ notes: z.ZodOptional<z.ZodString>;
1772
+ }, z.core.$strict>>;
1773
+ }, z.core.$strict>;
1774
+ type PptxIR = z.infer<typeof PptxIRSchema>;
1775
+ type Component = z.infer<typeof ComponentSchema>;
1776
+ type BackgroundSpec = z.infer<typeof BackgroundSpecSchema>;
1777
+ type Slide = z.infer<typeof SlideSchema>;
1778
+ type Assets = z.infer<typeof AssetsSchema>;
1779
+ type Meta = z.infer<typeof MetaSchema>;
1780
+
1781
+ interface ValidationIssue {
1782
+ path: string;
1783
+ message: string;
1784
+ /** 1-based slide number when the issue is scoped to a slide. */
1785
+ page?: number;
1786
+ /**
1787
+ * The offending slide's own `id` (`Slide.id`, `ir/index.ts`) — W5
1788
+ * whole-branch review finding 2: the README already claimed "validation
1789
+ * error messages reference [a slide] by [its] id"; this is what makes
1790
+ * that true. Set by every page-scoped issue producer
1791
+ * ({@link checkLayoutApplicability}, {@link checkBoundaryPageContent},
1792
+ * the content-quality-gate translation in {@link validateIr},
1793
+ * {@link checkDuplicateSlideIds}, {@link checkUnofferedSparsePins})
1794
+ * when the slide in question has an `id` — absent when the slide has
1795
+ * none (bare, pre-W5 IR) or the issue is deck-level, not scoped to any
1796
+ * single slide. {@link formatIssues} appends it in parens after the page
1797
+ * number.
1798
+ */
1799
+ slideId?: string;
1800
+ }
1801
+ interface ValidateResult {
1802
+ ok: boolean;
1803
+ ir?: PptxIR;
1804
+ errors: ValidationIssue[];
1805
+ /**
1806
+ * Human-readable "`path`: `alias` → `canonical`" entries for every
1807
+ * deterministic field-alias rewrite `validateIr` applied before parsing
1808
+ * (W5 task 4, `ir/field-aliases.ts`'s `normalizeComponentAliases`) — e.g. a
1809
+ * kpi item's `title` silently adopted as `label`. Present only when at
1810
+ * least one rewrite happened; informational, never gates `ok` on its own.
1811
+ */
1812
+ normalized?: string[];
1813
+ /**
1814
+ * Backward-compatible addition (borrow wave, Task 2 — dual-threshold
1815
+ * severity recalibration): warn-severity {@link checkIrQuality} findings
1816
+ * (editorial-budget codes — `missing_heading`/`long_heading`/`density`/
1817
+ * `bullets_overflow`/`bullet_item_long`/`big_number_no_kpi`) plus
1818
+ * {@link checkAssetReferences}'s dangling-`asset_id` findings (borrow wave,
1819
+ * Task 2, B5 — a reference to an `assets.images` key that doesn't exist),
1820
+ * formatted the same shape {@link formatIssues} already prints for `errors`
1821
+ * (page/id/path all included). Present only when at least one warn-severity
1822
+ * finding exists, and never gates `ok` on its own — that is exactly what
1823
+ * moved these findings off `errors` and onto here. Can be present alongside
1824
+ * a failing (`ok:false`) result too, whenever `checkIrQuality`/
1825
+ * `checkAssetReferences` themselves ran (an *earlier* hard gate — schema,
1826
+ * theme id, layout applicability, full-body exclusivity, boundary-page
1827
+ * content, duplicate ids, asset bytes, narrative — short-circuits before
1828
+ * either runs at all, so a deck rejected by one of those never reaches
1829
+ * this field either way, see `validateIr`'s own body for the exact gate
1830
+ * order). {@link formatWarnings} renders this array as CLI-ready
1831
+ * `"warning: ..."` lines.
1832
+ */
1833
+ warnings?: ValidationIssue[];
1834
+ }
1835
+ /**
1836
+ * Validate raw JSON against the IR schema, then — once it parses — resolve
1837
+ * `narrative` (`resolveNarrative`, spec §5: an unrecognized preset name is a
1838
+ * `narrative`-path error, page-less) and run the content-quality gate
1839
+ * (`checkIrQuality`, passed the resolved axes) against the parsed IR. Every
1840
+ * hard-gate stage (schema, theme id, layout applicability, full-body
1841
+ * exclusivity, boundary-page content, duplicate ids, asset bytes, narrative)
1842
+ * must pass for `ok: true`, same as before. Quality findings are reported
1843
+ * the same way as schema errors (page-scoped, 1-based).
1844
+ *
1845
+ * Dual-threshold severity (borrow wave, Task 2 — recalibrated from the prior
1846
+ * "any finding blocks" design): `checkIrQuality`'s own "warn" vs "error" tag
1847
+ * is now respected, not flattened. Only "error"-severity findings
1848
+ * (`empty_deck`, `bullet_item_overflow` — content genuinely lost: an empty
1849
+ * deck, or a bullet item long enough to hit `bullets.tsx`'s MIN_FONT floor
1850
+ * and actually get truncated) gate `ok`. "warn"-severity findings — the
1851
+ * editorial-budget codes (`missing_heading`/`long_heading`/`density`/
1852
+ * `bullets_overflow`/`bullet_item_long`/`big_number_no_kpi`) — surface on
1853
+ * {@link ValidateResult.warnings} instead: visible to every caller (the SDK,
1854
+ * the CLI's `validate`/`render` pre-flight), never blocking. This reverses
1855
+ * the prior posture (this comment used to read "any finding blocks... not
1856
+ * only 'error'-severity ones") because the evidence behind it did not hold:
1857
+ * a boundary scan (borrow-wave fact-report, Q3) found the editorial bullets
1858
+ * threshold blocking content roughly 3.5x below where `bullets.tsx`'s own
1859
+ * render safety net (2-line wrap, shrink to a 14px floor) actually starts
1860
+ * losing characters — so the hard gate was rejecting deck content that
1861
+ * would have rendered with zero visible defect, and a tight hard gate on an
1862
+ * editorial (not geometric) threshold taught truncate-content-to-pass
1863
+ * workarounds rather than catching real loss. `generatePptx`'s default path
1864
+ * inherits this unchanged (`if (!v.ok) throw`) — it already only ever
1865
+ * blocked on `ok`, never inspected `errors`/`warnings` directly.
1866
+ *
1867
+ * Before any of that, two deterministic pre-parse rewrite passes run, each
1868
+ * threading its rewrite notes onto `ValidateResult.normalized` on *every*
1869
+ * return path below via `withNormalized`, success or failure alike — neither
1870
+ * ever gates `ok` on its own:
1871
+ * - {@link normalizeComponentAliases} (W5 task 4): the component
1872
+ * field-name synonym rescue (kpi `title`→`label`, quote `content`→`text`,
1873
+ * …), scoped to `slides[]`. Only rewrites where the canonical key is
1874
+ * absent, so the schema parse below never sees an alias as an
1875
+ * "unrecognized key" in the first place.
1876
+ * - {@link normalizeNarrativeShape} (`./narrative`, T0b bench-evidence fix
1877
+ * 2 — see its own doc comment for the full rationale): the root-level
1878
+ * `narrative: {id: <preset>}` shape rescue. A model that just wrote
1879
+ * `theme: {id: "consulting"}` a few lines above pattern-matches the same
1880
+ * wrapper shape onto `narrative`; this rewrites it to the bare preset
1881
+ * string `resolveNarrative`'s string branch actually expects. Fires only
1882
+ * when `id` is the object's sole narrative-relevant key (none of
1883
+ * strategy/pacing/audience alongside it) — a mixed shape stays
1884
+ * unrescued, ambiguous, and hard-errors exactly as it did before this
1885
+ * pass existed.
1886
+ *
1887
+ * Both are the same class of fix — a weak-model *synonym* rescue (a name
1888
+ * drift for the component-alias pass, a *shape* drift for the narrative
1889
+ * pass) — which is the actual boundary the next paragraph's "no
1890
+ * old-vocabulary rescue" draws, not "no rewrite ever touches `narrative`."
1891
+ *
1892
+ * There is still deliberately no *old-vocabulary* rescue (spec §16,
1893
+ * reversing the now-superseded §15.4): a v4 document that still spells its
1894
+ * pre-rename vocabulary — `scenario` instead of `narrative`, `mode`/
1895
+ * `delivery` instead of `strategy`/`pacing`, or the old enum values
1896
+ * `"text"`/`"presentation"`/`"narrative"` — is not old-vocabulary
1897
+ * *compatibility*, it is exactly the vocabulary this rename retired, so it
1898
+ * hard-errors like any other unknown key or value: `scenario` fails the
1899
+ * schema's `.strict()` parse below as an unrecognized key, and an old enum
1900
+ * value (or the axis-key names `mode`/`delivery` inside `narrative`, which
1901
+ * the schema itself leaves open) fails `resolveNarrative`'s own runtime
1902
+ * check, listing the current values. `{id: <preset>}` was never a v3
1903
+ * `scenario` shape, so `normalizeNarrativeShape` above does not reopen this
1904
+ * door — it rescues a shape weak models invent by analogy to `theme.id`, not
1905
+ * a shape the pre-rename vocabulary ever spoke. `pptwise migrate`
1906
+ * (`ir/migrate.ts`) remains the sanctioned bridge for a genuine v3 document —
1907
+ * see the v3 hard reject below, which points there. Hard-erroring is not the
1908
+ * same as leaving the error message unhelpful, though: the schema-parse
1909
+ * branch below appends a rename hint to `scenario` and the rest of the
1910
+ * documented v2/v3 rename map (`blocks`/`variant`/`theme.override` —
1911
+ * `./ir/rename-hints.ts`, borrow-wave task 3) whenever the offending key
1912
+ * matches one, and a generic "belongs inside components[]" hint for any
1913
+ * other slide-level unrecognized key — message-layer annotation only, never
1914
+ * a second, silent rewrite path alongside the two passes above.
1915
+ *
1916
+ * Both pre-parse passes only ever run for a document already headed for the
1917
+ * v4 schema — an explicit `version: "2"` or `version: "3"` is hard-rejected
1918
+ * first, below, before either pass or any schema parse (spec §9.3: a v2/v3
1919
+ * document is never silently reinterpreted as v4).
1920
+ */
1921
+ declare function validateIr(input: unknown): ValidateResult;
1922
+ /**
1923
+ * `"page 2 (p-kpi) — path: message"` when the issue carries both a `page`
1924
+ * and a `slideId` (W5 whole-branch review finding 2 — the README's own
1925
+ * claim that a validation error "references [a slide] by [its] id", made
1926
+ * true) — the parenthesized id is appended only alongside a `page` number,
1927
+ * never on its own: a deck-level issue that happens to set a representative
1928
+ * `slideId` with no `page` ({@link checkDuplicateSlideIds} above) keeps its
1929
+ * pre-existing, unchanged format. Every other combination — `page` with no
1930
+ * `slideId` (an id-less slide), or neither — is byte-identical to before
1931
+ * this task.
1932
+ */
1933
+ declare function formatIssues(errors: ValidationIssue[]): string;
1934
+ interface ThemeInfo {
1935
+ id: string;
1936
+ label: string;
1937
+ colors: Record<string, unknown>;
1938
+ }
1939
+ /** Built-in theme catalog with labels and style color tokens. */
1940
+ declare function listThemes(): ThemeInfo[];
1941
+ /** JSON Schema for the IR — feed this to a model before it writes IR. */
1942
+ declare function irJsonSchema(): Record<string, unknown>;
1943
+ /** JSON Schema for style-token overrides (IR theme.style, --style files, config "style"). */
1944
+ declare function styleJsonSchema(): Record<string, unknown>;
1945
+
1946
+ /**
1947
+ * Public API surface: validateIr's own logic lives in `./validate-core`
1948
+ * (P2 browser-distribution wave, task 1 — see that file's own doc comment
1949
+ * for why) and is re-exported below unchanged, so every existing consumer
1950
+ * of this module keeps working exactly as before. `renderSlideSvg` and
1951
+ * `generatePptx` stay defined here — this is the one file allowed to reach
1952
+ * into `./svg/render-slide` and `./pptx/generate` (react-dom/server, jszip,
1953
+ * pptxgenjs).
1954
+ */
1955
+
1956
+ /** Render a single slide to standalone SVG markup (preview / self-check). */
1957
+ declare function renderSlideSvg(ir: PptxIR, slideIndex: number): string;
1958
+ /**
1959
+ * Full pipeline: validate → SVG → DrawingML → animation patches → pptx bytes.
1960
+ *
1961
+ * Two export gates, both "refuse to hand over a deliverable the caller
1962
+ * cannot see is broken, unless they say they know": `draft` skips the
1963
+ * unfilled-placeholder gate above, `allowDroppedContent` skips the
1964
+ * content-drop gate (`checkContentDropGate` in `./pptx/generate` — it lives
1965
+ * there because only a real layout can answer it, and the export renders
1966
+ * every slide there already).
1967
+ */
1968
+ declare function generatePptx(input: unknown, opts?: {
1969
+ draft?: boolean;
1970
+ allowDroppedContent?: boolean;
1971
+ }): Promise<Uint8Array>;
1972
+
1973
+ /**
1974
+ * The frozen IR v3 top-level shape (vocabulary-v4 rename, task 1 — spec
1975
+ * §9.3: "v3 已冻结... 顶层字段和枚举改名必须进入新的 IR 版本,不能在 v3
1976
+ * 内静默改变含义"). `./index.ts`'s `PptxIRSchema` is v4 now — this module
1977
+ * exists only so a genuinely v3-shaped document still has somewhere to parse
1978
+ * against: `migrateIrV3ToV4`'s input type (`./migrate.ts`), and the
1979
+ * v3-hard-reject path's own tests (constructing a *valid* v3 IR to prove the
1980
+ * reject fires on version alone, not on some other schema defect).
1981
+ *
1982
+ * `validateIr` (`src/api.ts`) never calls this schema itself — an incoming
1983
+ * v3 document (`version === "3"`) is hard-rejected before any schema parse
1984
+ * runs at all (spec §9.3), full stop. This schema is a migration-tooling and
1985
+ * test fixture, not a second accepted input shape.
1986
+ *
1987
+ * Every field but `version` and `scenario` is byte-identical to `./index.ts`'s
1988
+ * v4 `PptxIRSchema` (spec §9.1: "其余 IR 字段保持不变") — reuses the exact
1989
+ * same `ThemeSchema`/`MetaSchema`/`AssetsSchema`/`BrandSchema`/`SlideSchema`
1990
+ * instances rather than redefining them, so there is no way for this frozen
1991
+ * shape to silently drift from the fields it shares with v4.
1992
+ */
1993
+ declare const PptxIRV3Schema: z.ZodObject<{
1994
+ version: z.ZodDefault<z.ZodLiteral<"3">>;
1995
+ filename: z.ZodDefault<z.ZodString>;
1996
+ scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1997
+ theme: z.ZodDefault<z.ZodObject<{
1998
+ id: z.ZodDefault<z.ZodString>;
1999
+ style: z.ZodOptional<z.ZodObject<{
2000
+ colors: z.ZodOptional<z.ZodObject<{
2001
+ bg: z.ZodOptional<z.ZodString>;
2002
+ surface: z.ZodOptional<z.ZodString>;
2003
+ panel: z.ZodOptional<z.ZodString>;
2004
+ primary: z.ZodOptional<z.ZodString>;
2005
+ accent: z.ZodOptional<z.ZodString>;
2006
+ text: z.ZodOptional<z.ZodString>;
2007
+ muted: z.ZodOptional<z.ZodString>;
2008
+ border: z.ZodOptional<z.ZodString>;
2009
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
2010
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
2011
+ cardStroke: z.ZodOptional<z.ZodString>;
2012
+ }, z.core.$strict>>;
2013
+ fonts: z.ZodOptional<z.ZodObject<{
2014
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
2015
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
2016
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
2017
+ }, z.core.$strict>>;
2018
+ shape: z.ZodOptional<z.ZodObject<{
2019
+ radius: z.ZodOptional<z.ZodNumber>;
2020
+ gapScale: z.ZodOptional<z.ZodNumber>;
2021
+ typeScale: z.ZodOptional<z.ZodNumber>;
2022
+ }, z.core.$strict>>;
2023
+ }, z.core.$strict>>;
2024
+ brand: z.ZodOptional<z.ZodObject<{
2025
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
2026
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
2027
+ suppressFooterMeta: z.ZodOptional<z.ZodBoolean>;
2028
+ }, z.core.$strict>>;
2029
+ }, z.core.$strict>>;
2030
+ meta: z.ZodDefault<z.ZodObject<{
2031
+ organization: z.ZodOptional<z.ZodString>;
2032
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
2033
+ name: z.ZodString;
2034
+ role: z.ZodOptional<z.ZodString>;
2035
+ org: z.ZodOptional<z.ZodString>;
2036
+ }, z.core.$strict>>>;
2037
+ date: z.ZodOptional<z.ZodString>;
2038
+ version: z.ZodOptional<z.ZodString>;
2039
+ confidentiality: z.ZodOptional<z.ZodEnum<{
2040
+ public: "public";
2041
+ internal: "internal";
2042
+ confidential: "confidential";
2043
+ restricted: "restricted";
2044
+ }>>;
2045
+ contact: z.ZodOptional<z.ZodObject<{
2046
+ name: z.ZodOptional<z.ZodString>;
2047
+ email: z.ZodOptional<z.ZodString>;
2048
+ phone: z.ZodOptional<z.ZodString>;
2049
+ website: z.ZodOptional<z.ZodString>;
2050
+ }, z.core.$strict>>;
2051
+ copyright: z.ZodOptional<z.ZodString>;
2052
+ animation: z.ZodOptional<z.ZodObject<{
2053
+ transition: z.ZodOptional<z.ZodEnum<{
2054
+ push: "push";
2055
+ none: "none";
2056
+ fade: "fade";
2057
+ wipe: "wipe";
2058
+ }>>;
2059
+ elements: z.ZodOptional<z.ZodEnum<{
2060
+ none: "none";
2061
+ auto: "auto";
2062
+ }>>;
2063
+ }, z.core.$strict>>;
2064
+ }, z.core.$strict>>;
2065
+ assets: z.ZodDefault<z.ZodObject<{
2066
+ images: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
2067
+ src: z.ZodString;
2068
+ alt: z.ZodOptional<z.ZodString>;
2069
+ error: z.ZodOptional<z.ZodString>;
2070
+ }, z.core.$strict>>>;
2071
+ }, z.core.$strict>>;
2072
+ brand: z.ZodOptional<z.ZodObject<{
2073
+ logo_asset_id: z.ZodOptional<z.ZodString>;
2074
+ position: z.ZodOptional<z.ZodEnum<{
2075
+ tl: "tl";
2076
+ tr: "tr";
2077
+ bl: "bl";
2078
+ br: "br";
2079
+ }>>;
2080
+ }, z.core.$strict>>;
2081
+ chrome: z.ZodOptional<z.ZodEnum<{
2082
+ full: "full";
2083
+ "cover-only": "cover-only";
2084
+ minimal: "minimal";
2085
+ }>>;
2086
+ seed: z.ZodOptional<z.ZodNumber>;
2087
+ slides: z.ZodArray<z.ZodObject<{
2088
+ type: z.ZodDefault<z.ZodEnum<{
2089
+ content: "content";
2090
+ cover: "cover";
2091
+ chapter: "chapter";
2092
+ ending: "ending";
2093
+ }>>;
2094
+ id: z.ZodOptional<z.ZodString>;
2095
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
2096
+ layout: z.ZodOptional<z.ZodString>;
2097
+ beat: z.ZodOptional<z.ZodEnum<{
2098
+ dense: "dense";
2099
+ anchor: "anchor";
2100
+ breathing: "breathing";
2101
+ }>>;
2102
+ arrangement: z.ZodOptional<z.ZodEnum<{
2103
+ code: "code";
2104
+ quote: "quote";
2105
+ single: "single";
2106
+ two_column: "two_column";
2107
+ kpi_focus: "kpi_focus";
2108
+ image_focus: "image_focus";
2109
+ big_number: "big_number";
2110
+ assertion_evidence: "assertion_evidence";
2111
+ aside: "aside";
2112
+ }>>;
2113
+ heading: z.ZodOptional<z.ZodString>;
2114
+ subheading: z.ZodOptional<z.ZodString>;
2115
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
2116
+ type: z.ZodLiteral<"bullets">;
2117
+ items: z.ZodArray<z.ZodString>;
2118
+ style: z.ZodOptional<z.ZodEnum<{
2119
+ default: "default";
2120
+ checklist: "checklist";
2121
+ numbered: "numbered";
2122
+ plain: "plain";
2123
+ divided: "divided";
2124
+ }>>;
2125
+ }, z.core.$strict>, z.ZodObject<{
2126
+ type: z.ZodLiteral<"paragraph">;
2127
+ text: z.ZodString;
2128
+ }, z.core.$strict>, z.ZodObject<{
2129
+ type: z.ZodLiteral<"quote">;
2130
+ text: z.ZodString;
2131
+ attribution: z.ZodOptional<z.ZodString>;
2132
+ }, z.core.$strict>, z.ZodObject<{
2133
+ type: z.ZodLiteral<"callout">;
2134
+ variant: z.ZodEnum<{
2135
+ info: "info";
2136
+ warn: "warn";
2137
+ tip: "tip";
2138
+ }>;
2139
+ text: z.ZodString;
2140
+ icon: z.ZodOptional<z.ZodEnum<{
2141
+ [x: string]: string;
2142
+ }>>;
2143
+ }, z.core.$strict>, z.ZodObject<{
2144
+ type: z.ZodLiteral<"code">;
2145
+ language: z.ZodString;
2146
+ code: z.ZodString;
2147
+ }, z.core.$strict>, z.ZodObject<{
2148
+ type: z.ZodLiteral<"kpi_cards">;
2149
+ items: z.ZodArray<z.ZodObject<{
2150
+ value: z.ZodString;
2151
+ unit: z.ZodOptional<z.ZodString>;
2152
+ label: z.ZodString;
2153
+ delta: z.ZodOptional<z.ZodEnum<{
2154
+ flat: "flat";
2155
+ up: "up";
2156
+ down: "down";
2157
+ }>>;
2158
+ icon: z.ZodOptional<z.ZodEnum<{
2159
+ [x: string]: string;
2160
+ }>>;
2161
+ source: z.ZodOptional<z.ZodString>;
2162
+ }, z.core.$strict>>;
2163
+ }, z.core.$strict>, z.ZodObject<{
2164
+ type: z.ZodLiteral<"chart">;
2165
+ chart_type: z.ZodEnum<{
2166
+ line: "line";
2167
+ donut: "donut";
2168
+ dumbbell: "dumbbell";
2169
+ funnel: "funnel";
2170
+ gauge: "gauge";
2171
+ bar: "bar";
2172
+ pie: "pie";
2173
+ scatter: "scatter";
2174
+ area: "area";
2175
+ }>;
2176
+ direction: z.ZodOptional<z.ZodEnum<{
2177
+ horizontal: "horizontal";
2178
+ vertical: "vertical";
2179
+ }>>;
2180
+ style: z.ZodOptional<z.ZodEnum<{
2181
+ donut: "donut";
2182
+ }>>;
2183
+ center_total: z.ZodOptional<z.ZodBoolean>;
2184
+ gauge: z.ZodOptional<z.ZodObject<{
2185
+ min: z.ZodOptional<z.ZodNumber>;
2186
+ max: z.ZodOptional<z.ZodNumber>;
2187
+ }, z.core.$strict>>;
2188
+ axes: z.ZodOptional<z.ZodObject<{
2189
+ x_title: z.ZodOptional<z.ZodString>;
2190
+ y_title: z.ZodOptional<z.ZodString>;
2191
+ show_grid: z.ZodOptional<z.ZodBoolean>;
2192
+ }, z.core.$strict>>;
2193
+ series: z.ZodArray<z.ZodObject<{
2194
+ name: z.ZodString;
2195
+ data: z.ZodArray<z.ZodObject<{
2196
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
2197
+ y: z.ZodNumber;
2198
+ size: z.ZodOptional<z.ZodNumber>;
2199
+ }, z.core.$strict>>;
2200
+ }, z.core.$strict>>;
2201
+ }, z.core.$strict>, z.ZodObject<{
2202
+ type: z.ZodLiteral<"flowchart">;
2203
+ nodes: z.ZodArray<z.ZodObject<{
2204
+ id: z.ZodString;
2205
+ label: z.ZodString;
2206
+ kind: z.ZodOptional<z.ZodEnum<{
2207
+ rect: "rect";
2208
+ diamond: "diamond";
2209
+ round: "round";
2210
+ }>>;
2211
+ }, z.core.$strict>>;
2212
+ edges: z.ZodArray<z.ZodObject<{
2213
+ from: z.ZodString;
2214
+ to: z.ZodString;
2215
+ label: z.ZodOptional<z.ZodString>;
2216
+ }, z.core.$strict>>;
2217
+ direction: z.ZodOptional<z.ZodEnum<{
2218
+ TB: "TB";
2219
+ TD: "TD";
2220
+ BT: "BT";
2221
+ LR: "LR";
2222
+ RL: "RL";
2223
+ }>>;
2224
+ }, z.core.$strict>, z.ZodObject<{
2225
+ type: z.ZodLiteral<"architecture">;
2226
+ layers: z.ZodArray<z.ZodObject<{
2227
+ title: z.ZodString;
2228
+ items: z.ZodArray<z.ZodString>;
2229
+ }, z.core.$strict>>;
2230
+ direction: z.ZodOptional<z.ZodEnum<{
2231
+ top_down: "top_down";
2232
+ bottom_up: "bottom_up";
2233
+ }>>;
2234
+ }, z.core.$strict>, z.ZodObject<{
2235
+ type: z.ZodLiteral<"timeline">;
2236
+ layout: z.ZodOptional<z.ZodEnum<{
2237
+ horizontal: "horizontal";
2238
+ vertical: "vertical";
2239
+ }>>;
2240
+ milestones: z.ZodArray<z.ZodObject<{
2241
+ date: z.ZodString;
2242
+ title: z.ZodString;
2243
+ desc: z.ZodOptional<z.ZodString>;
2244
+ highlight: z.ZodOptional<z.ZodBoolean>;
2245
+ }, z.core.$strict>>;
2246
+ }, z.core.$strict>, z.ZodObject<{
2247
+ type: z.ZodLiteral<"comparison">;
2248
+ columns: z.ZodArray<z.ZodString>;
2249
+ rows: z.ZodArray<z.ZodObject<{
2250
+ label: z.ZodString;
2251
+ cells: z.ZodArray<z.ZodString>;
2252
+ }, z.core.$strict>>;
2253
+ }, z.core.$strict>, z.ZodObject<{
2254
+ type: z.ZodLiteral<"icon_cards">;
2255
+ items: z.ZodArray<z.ZodObject<{
2256
+ icon: z.ZodEnum<{
2257
+ [x: string]: string;
2258
+ }>;
2259
+ title: z.ZodString;
2260
+ text: z.ZodString;
2261
+ }, z.core.$strict>>;
2262
+ }, z.core.$strict>, z.ZodObject<{
2263
+ type: z.ZodLiteral<"row_cards">;
2264
+ items: z.ZodArray<z.ZodObject<{
2265
+ icon: z.ZodOptional<z.ZodEnum<{
2266
+ [x: string]: string;
2267
+ }>>;
2268
+ title: z.ZodString;
2269
+ text: z.ZodOptional<z.ZodString>;
2270
+ sub: z.ZodOptional<z.ZodString>;
2271
+ highlight: z.ZodOptional<z.ZodBoolean>;
2272
+ }, z.core.$strict>>;
2273
+ }, z.core.$strict>, z.ZodObject<{
2274
+ type: z.ZodLiteral<"steps">;
2275
+ items: z.ZodArray<z.ZodObject<{
2276
+ title: z.ZodString;
2277
+ text: z.ZodString;
2278
+ }, z.core.$strict>>;
2279
+ }, z.core.$strict>, z.ZodObject<{
2280
+ type: z.ZodLiteral<"rings">;
2281
+ items: z.ZodArray<z.ZodObject<{
2282
+ label: z.ZodString;
2283
+ desc: z.ZodOptional<z.ZodString>;
2284
+ }, z.core.$strict>>;
2285
+ }, z.core.$strict>, z.ZodObject<{
2286
+ type: z.ZodLiteral<"numbered_cards">;
2287
+ items: z.ZodArray<z.ZodObject<{
2288
+ title: z.ZodString;
2289
+ text: z.ZodOptional<z.ZodString>;
2290
+ sub: z.ZodOptional<z.ZodString>;
2291
+ }, z.core.$strict>>;
2292
+ }, z.core.$strict>, z.ZodObject<{
2293
+ type: z.ZodLiteral<"roadmap">;
2294
+ items: z.ZodArray<z.ZodObject<{
2295
+ title: z.ZodString;
2296
+ period: z.ZodOptional<z.ZodString>;
2297
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
2298
+ label: z.ZodString;
2299
+ value: z.ZodString;
2300
+ }, z.core.$strict>>>;
2301
+ }, z.core.$strict>>;
2302
+ }, z.core.$strict>, z.ZodObject<{
2303
+ type: z.ZodLiteral<"matrix">;
2304
+ x_title: z.ZodOptional<z.ZodString>;
2305
+ y_title: z.ZodOptional<z.ZodString>;
2306
+ cols: z.ZodNumber;
2307
+ items: z.ZodArray<z.ZodObject<{
2308
+ title: z.ZodString;
2309
+ tag: z.ZodOptional<z.ZodString>;
2310
+ tone: z.ZodOptional<z.ZodEnum<{
2311
+ info: "info";
2312
+ neutral: "neutral";
2313
+ accent: "accent";
2314
+ }>>;
2315
+ }, z.core.$strict>>;
2316
+ }, z.core.$strict>, z.ZodObject<{
2317
+ type: z.ZodLiteral<"insight_panel">;
2318
+ title: z.ZodString;
2319
+ rows: z.ZodArray<z.ZodObject<{
2320
+ label: z.ZodString;
2321
+ text: z.ZodString;
2322
+ }, z.core.$strict>>;
2323
+ footnote: z.ZodOptional<z.ZodString>;
2324
+ }, z.core.$strict>, z.ZodObject<{
2325
+ type: z.ZodLiteral<"verdict_banner">;
2326
+ text: z.ZodString;
2327
+ tone: z.ZodEnum<{
2328
+ positive: "positive";
2329
+ warning: "warning";
2330
+ neutral: "neutral";
2331
+ }>;
2332
+ icon: z.ZodOptional<z.ZodEnum<{
2333
+ [x: string]: string;
2334
+ }>>;
2335
+ }, z.core.$strict>, z.ZodObject<{
2336
+ type: z.ZodLiteral<"citation">;
2337
+ sources: z.ZodArray<z.ZodObject<{
2338
+ label: z.ZodString;
2339
+ url: z.ZodOptional<z.ZodString>;
2340
+ ref: z.ZodOptional<z.ZodString>;
2341
+ }, z.core.$strict>>;
2342
+ }, z.core.$strict>, z.ZodObject<{
2343
+ type: z.ZodLiteral<"image">;
2344
+ asset_id: z.ZodString;
2345
+ caption: z.ZodOptional<z.ZodString>;
2346
+ fit: z.ZodDefault<z.ZodEnum<{
2347
+ contain: "contain";
2348
+ cover: "cover";
2349
+ }>>;
2350
+ }, z.core.$strict>, z.ZodObject<{
2351
+ type: z.ZodLiteral<"image_grid">;
2352
+ items: z.ZodArray<z.ZodObject<{
2353
+ asset_id: z.ZodString;
2354
+ caption: z.ZodOptional<z.ZodString>;
2355
+ }, z.core.$strict>>;
2356
+ emphasis: z.ZodOptional<z.ZodEnum<{
2357
+ none: "none";
2358
+ first: "first";
2359
+ }>>;
2360
+ }, z.core.$strict>, z.ZodObject<{
2361
+ type: z.ZodLiteral<"image_compare">;
2362
+ left: z.ZodObject<{
2363
+ asset_id: z.ZodString;
2364
+ label: z.ZodString;
2365
+ }, z.core.$strict>;
2366
+ right: z.ZodObject<{
2367
+ asset_id: z.ZodString;
2368
+ label: z.ZodString;
2369
+ }, z.core.$strict>;
2370
+ style: z.ZodOptional<z.ZodEnum<{
2371
+ vs: "vs";
2372
+ before_after: "before_after";
2373
+ }>>;
2374
+ }, z.core.$strict>, z.ZodObject<{
2375
+ type: z.ZodLiteral<"swot">;
2376
+ strengths: z.ZodArray<z.ZodString>;
2377
+ weaknesses: z.ZodArray<z.ZodString>;
2378
+ opportunities: z.ZodArray<z.ZodString>;
2379
+ threats: z.ZodArray<z.ZodString>;
2380
+ labels: z.ZodOptional<z.ZodObject<{
2381
+ strengths: z.ZodOptional<z.ZodString>;
2382
+ weaknesses: z.ZodOptional<z.ZodString>;
2383
+ opportunities: z.ZodOptional<z.ZodString>;
2384
+ threats: z.ZodOptional<z.ZodString>;
2385
+ }, z.core.$strict>>;
2386
+ }, z.core.$strict>, z.ZodObject<{
2387
+ type: z.ZodLiteral<"bmc">;
2388
+ key_partners: z.ZodArray<z.ZodString>;
2389
+ key_activities: z.ZodArray<z.ZodString>;
2390
+ key_resources: z.ZodArray<z.ZodString>;
2391
+ value_propositions: z.ZodArray<z.ZodString>;
2392
+ customer_relationships: z.ZodArray<z.ZodString>;
2393
+ channels: z.ZodArray<z.ZodString>;
2394
+ customer_segments: z.ZodArray<z.ZodString>;
2395
+ cost_structure: z.ZodArray<z.ZodString>;
2396
+ revenue_streams: z.ZodArray<z.ZodString>;
2397
+ }, z.core.$strict>, z.ZodObject<{
2398
+ type: z.ZodLiteral<"waterfall">;
2399
+ items: z.ZodArray<z.ZodObject<{
2400
+ label: z.ZodString;
2401
+ value: z.ZodNumber;
2402
+ kind: z.ZodOptional<z.ZodEnum<{
2403
+ delta: "delta";
2404
+ total: "total";
2405
+ }>>;
2406
+ }, z.core.$strict>>;
2407
+ unit: z.ZodOptional<z.ZodString>;
2408
+ }, z.core.$strict>, z.ZodObject<{
2409
+ type: z.ZodLiteral<"gantt">;
2410
+ items: z.ZodArray<z.ZodObject<{
2411
+ label: z.ZodString;
2412
+ start: z.ZodNumber;
2413
+ end: z.ZodNumber;
2414
+ }, z.core.$strict>>;
2415
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
2416
+ }, z.core.$strict>, z.ZodObject<{
2417
+ type: z.ZodLiteral<"pest">;
2418
+ political: z.ZodObject<{
2419
+ title: z.ZodOptional<z.ZodString>;
2420
+ items: z.ZodArray<z.ZodString>;
2421
+ }, z.core.$strict>;
2422
+ economic: z.ZodObject<{
2423
+ title: z.ZodOptional<z.ZodString>;
2424
+ items: z.ZodArray<z.ZodString>;
2425
+ }, z.core.$strict>;
2426
+ social: z.ZodObject<{
2427
+ title: z.ZodOptional<z.ZodString>;
2428
+ items: z.ZodArray<z.ZodString>;
2429
+ }, z.core.$strict>;
2430
+ technological: z.ZodObject<{
2431
+ title: z.ZodOptional<z.ZodString>;
2432
+ items: z.ZodArray<z.ZodString>;
2433
+ }, z.core.$strict>;
2434
+ }, z.core.$strict>, z.ZodObject<{
2435
+ type: z.ZodLiteral<"five_forces">;
2436
+ rivalry: z.ZodObject<{
2437
+ label: z.ZodOptional<z.ZodString>;
2438
+ intensity: z.ZodOptional<z.ZodEnum<{
2439
+ low: "low";
2440
+ medium: "medium";
2441
+ high: "high";
2442
+ }>>;
2443
+ items: z.ZodArray<z.ZodString>;
2444
+ }, z.core.$strict>;
2445
+ new_entrants: z.ZodObject<{
2446
+ label: z.ZodOptional<z.ZodString>;
2447
+ intensity: z.ZodOptional<z.ZodEnum<{
2448
+ low: "low";
2449
+ medium: "medium";
2450
+ high: "high";
2451
+ }>>;
2452
+ items: z.ZodArray<z.ZodString>;
2453
+ }, z.core.$strict>;
2454
+ supplier_power: z.ZodObject<{
2455
+ label: z.ZodOptional<z.ZodString>;
2456
+ intensity: z.ZodOptional<z.ZodEnum<{
2457
+ low: "low";
2458
+ medium: "medium";
2459
+ high: "high";
2460
+ }>>;
2461
+ items: z.ZodArray<z.ZodString>;
2462
+ }, z.core.$strict>;
2463
+ buyer_power: z.ZodObject<{
2464
+ label: z.ZodOptional<z.ZodString>;
2465
+ intensity: z.ZodOptional<z.ZodEnum<{
2466
+ low: "low";
2467
+ medium: "medium";
2468
+ high: "high";
2469
+ }>>;
2470
+ items: z.ZodArray<z.ZodString>;
2471
+ }, z.core.$strict>;
2472
+ substitutes: z.ZodObject<{
2473
+ label: z.ZodOptional<z.ZodString>;
2474
+ intensity: z.ZodOptional<z.ZodEnum<{
2475
+ low: "low";
2476
+ medium: "medium";
2477
+ high: "high";
2478
+ }>>;
2479
+ items: z.ZodArray<z.ZodString>;
2480
+ }, z.core.$strict>;
2481
+ }, z.core.$strict>, z.ZodObject<{
2482
+ type: z.ZodLiteral<"heatmap">;
2483
+ x_labels: z.ZodArray<z.ZodString>;
2484
+ y_labels: z.ZodArray<z.ZodString>;
2485
+ values: z.ZodArray<z.ZodArray<z.ZodNumber>>;
2486
+ domain: z.ZodOptional<z.ZodObject<{
2487
+ min: z.ZodNumber;
2488
+ max: z.ZodNumber;
2489
+ }, z.core.$strict>>;
2490
+ show_values: z.ZodOptional<z.ZodBoolean>;
2491
+ x_title: z.ZodOptional<z.ZodString>;
2492
+ y_title: z.ZodOptional<z.ZodString>;
2493
+ }, z.core.$strict>, z.ZodObject<{
2494
+ type: z.ZodLiteral<"sankey">;
2495
+ nodes: z.ZodArray<z.ZodObject<{
2496
+ id: z.ZodString;
2497
+ label: z.ZodString;
2498
+ }, z.core.$strict>>;
2499
+ links: z.ZodArray<z.ZodObject<{
2500
+ from: z.ZodString;
2501
+ to: z.ZodString;
2502
+ value: z.ZodNumber;
2503
+ }, z.core.$strict>>;
2504
+ }, z.core.$strict>, z.ZodObject<{
2505
+ type: z.ZodLiteral<"data_table">;
2506
+ columns: z.ZodArray<z.ZodObject<{
2507
+ key: z.ZodString;
2508
+ label: z.ZodString;
2509
+ align: z.ZodOptional<z.ZodEnum<{
2510
+ left: "left";
2511
+ right: "right";
2512
+ center: "center";
2513
+ }>>;
2514
+ }, z.core.$strict>>;
2515
+ rows: z.ZodArray<z.ZodObject<{
2516
+ cells: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
2517
+ emphasis: z.ZodOptional<z.ZodEnum<{
2518
+ highlight: "highlight";
2519
+ total: "total";
2520
+ }>>;
2521
+ }, z.core.$strict>>;
2522
+ source: z.ZodOptional<z.ZodString>;
2523
+ }, z.core.$strict>, z.ZodObject<{
2524
+ type: z.ZodLiteral<"device_mockup">;
2525
+ device: z.ZodEnum<{
2526
+ phone: "phone";
2527
+ browser: "browser";
2528
+ }>;
2529
+ asset_id: z.ZodString;
2530
+ caption: z.ZodOptional<z.ZodString>;
2531
+ url: z.ZodOptional<z.ZodString>;
2532
+ }, z.core.$strict>, z.ZodObject<{
2533
+ type: z.ZodLiteral<"cycle">;
2534
+ title: z.ZodOptional<z.ZodString>;
2535
+ items: z.ZodArray<z.ZodObject<{
2536
+ label: z.ZodString;
2537
+ description: z.ZodOptional<z.ZodString>;
2538
+ }, z.core.$strict>>;
2539
+ }, z.core.$strict>, z.ZodObject<{
2540
+ type: z.ZodLiteral<"people_cards">;
2541
+ title: z.ZodOptional<z.ZodString>;
2542
+ people: z.ZodArray<z.ZodObject<{
2543
+ name: z.ZodString;
2544
+ role: z.ZodOptional<z.ZodString>;
2545
+ org: z.ZodOptional<z.ZodString>;
2546
+ }, z.core.$strict>>;
2547
+ }, z.core.$strict>, z.ZodObject<{
2548
+ type: z.ZodLiteral<"tag_row">;
2549
+ title: z.ZodOptional<z.ZodString>;
2550
+ items: z.ZodArray<z.ZodString>;
2551
+ emphasis: z.ZodOptional<z.ZodEnum<{
2552
+ none: "none";
2553
+ first: "first";
2554
+ }>>;
2555
+ }, z.core.$strict>], "type">>>;
2556
+ background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
2557
+ kind: z.ZodLiteral<"color">;
2558
+ value: z.ZodString;
2559
+ }, z.core.$strict>, z.ZodObject<{
2560
+ kind: z.ZodLiteral<"gradient">;
2561
+ from: z.ZodString;
2562
+ to: z.ZodString;
2563
+ direction: z.ZodOptional<z.ZodEnum<{
2564
+ tb: "tb";
2565
+ lr: "lr";
2566
+ diagonal: "diagonal";
2567
+ }>>;
2568
+ }, z.core.$strict>, z.ZodObject<{
2569
+ kind: z.ZodLiteral<"asset">;
2570
+ asset_id: z.ZodString;
2571
+ overlay: z.ZodOptional<z.ZodObject<{
2572
+ color: z.ZodString;
2573
+ opacity: z.ZodNumber;
2574
+ }, z.core.$strict>>;
2575
+ fit: z.ZodOptional<z.ZodEnum<{
2576
+ contain: "contain";
2577
+ cover: "cover";
2578
+ }>>;
2579
+ }, z.core.$strict>], "kind">>;
2580
+ decor: z.ZodOptional<z.ZodObject<{
2581
+ kind: z.ZodEnum<{
2582
+ big_number: "big_number";
2583
+ corner_tag: "corner_tag";
2584
+ rule_line: "rule_line";
2585
+ quote_marks: "quote_marks";
2586
+ geo_dots: "geo_dots";
2587
+ }>;
2588
+ intensity: z.ZodOptional<z.ZodEnum<{
2589
+ subtle: "subtle";
2590
+ normal: "normal";
2591
+ }>>;
2592
+ text: z.ZodOptional<z.ZodString>;
2593
+ }, z.core.$strict>>;
2594
+ image_side: z.ZodOptional<z.ZodEnum<{
2595
+ left: "left";
2596
+ right: "right";
2597
+ }>>;
2598
+ footnote: z.ZodOptional<z.ZodString>;
2599
+ notes: z.ZodOptional<z.ZodString>;
2600
+ }, z.core.$strict>>;
2601
+ }, z.core.$strict>;
2602
+ type PptxIRV3 = z.infer<typeof PptxIRV3Schema>;
2603
+
2604
+ /**
2605
+ * Deterministic, pure IR v3 → v4 migration (spec §9.1). Field-for-field,
2606
+ * value-for-value per the mapping in {@link migrateNarrativeInput}'s
2607
+ * docstring — every field this function doesn't touch (`filename`, `theme`,
2608
+ * `meta`, `assets`, `brand`, `seed`, `slides`) carries across by the exact
2609
+ * same reference it came in with, unchanged (spec §9.1: "其余 IR 字段保持不
2610
+ * 变"; spec §10: no weight/budget/selection/render change is in scope for
2611
+ * this migration, ever).
2612
+ *
2613
+ * Exported from the SDK surface (`src/index.ts`) as the deterministic
2614
+ * migration primitive the `pptwise migrate` CLI command (task 2) wraps —
2615
+ * this function itself does no I/O and never runs a model, per spec §9.3's
2616
+ * "只做已声明的结构映射,不运行模型,不重写内容,不重新选择 layout".
2617
+ *
2618
+ * Takes an already-parsed `PptxIRV3` (i.e. `PptxIRV3Schema.parse(...)`'s
2619
+ * output, defaults already applied) rather than raw `unknown` JSON — schema
2620
+ * validation of the v3 input is the caller's job (the CLI parses-then-
2621
+ * migrates; `validateIr`'s own v3 path hard-rejects before ever reaching
2622
+ * this function, spec §9.3, so `validateIr` itself never calls this).
2623
+ */
2624
+ declare function migrateIrV3ToV4(v3: PptxIRV3): PptxIR;
2625
+
2626
+ type LayoutType = "cover" | "chapter" | "content" | "ending";
2627
+ interface StyleColors {
2628
+ bg: string;
2629
+ surface: string;
2630
+ panel?: string;
2631
+ primary: string;
2632
+ accent: string;
2633
+ text: string;
2634
+ muted: string;
2635
+ border?: string;
2636
+ chartPalette: string[];
2637
+ /**
2638
+ * Optional accent color pool for themes whose layout grammar cycles through
2639
+ * multiple accent colors (e.g. tech card corner stripes). Themes that
2640
+ * only use a single accent color can omit this.
2641
+ */
2642
+ accentPool?: string[];
2643
+ /**
2644
+ * Optional hairline stroke color for the shared "surface card" shells
2645
+ * (kpi_cards / icon_cards / callout — see components/kpi.tsx,
2646
+ * components/icon-cards.tsx, components/callout.tsx). Those component renderers are
2647
+ * shared across every theme and must stay theme-agnostic (no
2648
+ * `if (themeId === ...)` branching), so a theme that wants its cards
2649
+ * outlined sets this token instead — the renderers just consult it and add
2650
+ * a 1px stroke when it's defined. Omitted (`undefined`, the default) draws
2651
+ * no stroke, so every theme that doesn't set it keeps its current
2652
+ * fill-only card unchanged.
2653
+ */
2654
+ cardStroke?: string;
2655
+ /**
2656
+ * Optional semantic-role colors — the "this is an error", "this is a
2657
+ * caution", "this is a good result" trio that shared component renderers
2658
+ * used to bake in as literal hexes (`callout`'s warn rule and icon in
2659
+ * components/callout.tsx, `kpi_cards`' delta arrow in components/kpi.tsx).
2660
+ * Same reasoning as `cardStroke` above: those renderers are shared across
2661
+ * every theme and must stay theme-agnostic (no `if (themeId === ...)`
2662
+ * branching), so a theme that wants its own red or green sets these tokens
2663
+ * and the renderers read them through `resolveSemanticColor`
2664
+ * (`src/svg/ink.ts`), which owns the fallback order.
2665
+ *
2666
+ * All three are optional on the type, and an omitted role resolves to
2667
+ * exactly the hex its renderers baked in before this channel existed —
2668
+ * `#DC2626` for `danger`, `#16A34A` for `success`, and `warning` follows
2669
+ * whatever `danger` resolves to. So a theme that sets none of them renders
2670
+ * byte-for-byte as it did before the tokens were added.
2671
+ *
2672
+ * Every one of the 22 built-in themes does set all three (visual review
2673
+ * round 4 ruled that the alert color must belong to the theme's own
2674
+ * palette, not be one universal red), so in practice only a `--theme-file`
2675
+ * brand theme reaches the fallbacks. `danger` and `success` are calibrated
2676
+ * to clear 4.5:1 on their own theme's `surface` — they render as the kpi
2677
+ * delta arrow's *text*, which `accessibleInk` would otherwise demote to
2678
+ * neutral ink — while `warning` only has to clear the 3:1 non-text floor,
2679
+ * since `callout` paints it as a rule and an icon. Both floors are pinned
2680
+ * in `svg/audit/full-matrix-contrast.test.ts`.
2681
+ */
2682
+ danger?: string;
2683
+ warning?: string;
2684
+ success?: string;
2685
+ }
2686
+ interface StyleFonts {
2687
+ heading: string[];
2688
+ body: string[];
2689
+ mono?: string[];
2690
+ }
2691
+ interface StyleTokens {
2692
+ id: string;
2693
+ allowCustomBackground?: boolean;
2694
+ colors: StyleColors;
2695
+ fonts: StyleFonts;
2696
+ shape?: StyleShape;
2697
+ defaultBackgrounds: Record<LayoutType, BackgroundSpec>;
2698
+ }
2699
+ /**
2700
+ * 主题细节 shape token(2026-07-10 用户立项,spec:
2701
+ * .issues/specs/2026-07-10-pptx-theme-detail-tokens-design.md)。
2702
+ * 全部可选——缺省时各消费点沿用自己的 baked 值(零观感)。
2703
+ */
2704
+ interface StyleShape {
2705
+ /** 卡片/横幅圆角 px(统一值)。缺省=各消费点原 baked 值(kpi 8/architecture 6/白卡 14 等)。 */
2706
+ radius?: number;
2707
+ /** 块间距缩放(1=现 BLOCK_GAP)。建议范围 [0.8, 1.3]。 */
2708
+ gapScale?: number;
2709
+ /**
2710
+ * Heading and display size multiplier. Applied to the layout's designed
2711
+ * max size *before* heading-fit shrinks to the box (`fitHeadingLines` /
2712
+ * `fitHeadingPt`, `src/svg/heading-fit.ts`). Cover, chapter, ending, and
2713
+ * pin-only speech pages pass it through. Content layouts that share the
2714
+ * page with a body stack omit it, so density still holds. Body, meta,
2715
+ * kicker, and footnote sizes are untouched. Omit (or `1`) for a
2716
+ * byte-identical no-op. Fit still owns the floor: `minPt` is not scaled,
2717
+ * so a long title still shrinks instead of overflowing. Suggested range
2718
+ * `[0.8, 1.6]`.
2719
+ */
2720
+ typeScale?: number;
2721
+ /**
2722
+ * Optional cover-layout knobs. Layouts read these. They never branch on
2723
+ * theme id. Omit every field and the layout uses its own file constants
2724
+ * (`band-title`: y=260, h=200, start, no mark, no wave.
2725
+ * `corner-wedge`: peakY=340, startX=980, middle, meta on paper.
2726
+ * `poster-center`: no kicker, primary bar, centered meta.
2727
+ * `left-anchor`: corner triangle on, title vertically centered.
2728
+ * `constellation`: last line on y520, 84×4 bar.
2729
+ * `tone-adaptive-header`: title 92, right-bottom date shown.
2730
+ * `editorial-masthead`: middle anchor, no kicker).
2731
+ * Not part of `StyleOverrideSchema`. IR overrides stay
2732
+ * `{ radius, gapScale, typeScale }`.
2733
+ */
2734
+ cover?: {
2735
+ textAnchor?: "start" | "middle";
2736
+ bandY?: number;
2737
+ bandH?: number;
2738
+ /** Small accent square on the band (enterprise). */
2739
+ bandMark?: boolean;
2740
+ /** Accent q-curve under the band (classroom). */
2741
+ bandWave?: boolean;
2742
+ wedgePeakY?: number;
2743
+ wedgeStartX?: number;
2744
+ /** Meta in readableOn(primary) inside the wedge (ember). */
2745
+ metaInWedge?: boolean;
2746
+ /** Independent kicker row above the title. Default false. poster-center + editorial-masthead. */
2747
+ showKicker?: boolean;
2748
+ /** Short bar fill. Default "primary". poster-center. */
2749
+ barFill?: "primary" | "accent";
2750
+ /** Merged meta placement. Default "center". poster-center. */
2751
+ metaPlacement?: "center" | "bottom-left" | "bottom-right" | "top" | "none";
2752
+ /** Corner triangle on the color block. Default true. left-anchor. */
2753
+ showCornerTriangle?: boolean;
2754
+ /** Title vertical alignment inside the block. Default "center". left-anchor. */
2755
+ titleBlockAlign?: "center" | "upper";
2756
+ /** Org as in-block kicker. Default false. left-anchor. */
2757
+ showInBlockKicker?: boolean;
2758
+ /** Pin last title line to y520. Default true. constellation. */
2759
+ titleBottomAnchor?: boolean;
2760
+ /** Rule under the title. Default "bar". constellation. */
2761
+ ruleStyle?: "bar" | "star-chain";
2762
+ /** Cover title size in px. Default 92. tone-adaptive-header. */
2763
+ titleSize?: number;
2764
+ /** Hide the right-bottom date/version slot. Default false. tone-adaptive-header. */
2765
+ hideRightMeta?: boolean;
2766
+ /** verdict-index. Omit for the restore-wave-1 constants. */
2767
+ verdictTitleY?: number;
2768
+ verdictTitleSize?: number;
2769
+ verdictKickerY?: number;
2770
+ verdictColNumY?: number;
2771
+ verdictColBodyY?: number;
2772
+ verdictFootY?: number;
2773
+ verdictFootRule?: boolean;
2774
+ /** corner-wedge inner overlay (ember dual wedge). Omit for the single overlay. */
2775
+ wedgeInnerStartX?: number;
2776
+ wedgeInnerPeakY?: number;
2777
+ };
2778
+ }
2779
+
2780
+ type MotifId = "banner-motif" | "rail-motif" | "poster-motif" | "constellation-motif" | "corner-ornament-motif" | "tone-adaptive-motif" | "campaign-motif" | "classroom-motif" | "ink-motif" | "luxe-motif" | "enterprise-motif" | "heritage-motif" | "pulse-motif" | "terra-motif" | "ember-motif" | "vermilion-motif" | "crayon-motif" | "arena-motif" | "lecture-motif" | "swiss-motif" | "memo-motif" | "playbill-motif";
2781
+
2782
+ /**
2783
+ * A theme = distributable bundle: `style` (style tokens) + `brand` (brand
2784
+ * frame) + affinity tags (filled in W4).
2785
+ *
2786
+ * `id` is a plain `string`, not `CanonicalThemeId` — the 13 builtins satisfy
2787
+ * this (`CanonicalThemeId` is a subtype of `string`), but `registerTheme`
2788
+ * below (W3 task 4's SDK registration seam) must also accept ids outside that
2789
+ * closed union.
2790
+ */
2791
+ interface ThemeDefinition {
2792
+ id: string;
2793
+ style: StyleTokens;
2794
+ brand: BrandConfig;
2795
+ tags: readonly string[];
2796
+ /**
2797
+ * 主题的「选择权」配置(spec §3 theme.layouts 命名裁决;W2 任务 2 由
2798
+ * src/themes/manifest.ts〔已删除〕的旧选择权类型原地迁居于此)——四页型
2799
+ * 各自允许哪些 layout 参与自动选型。排印/色彩在 style,这里只放集合。
2800
+ * **W4 全集放开**(spec §3「缺省 = 全集,策展收窄塑造个性」,design
2801
+ * decision 7):十三内置主题四页型默认均为 {@link fullLayoutSet} 的
2802
+ * 全集。design decision 7/8 曾经的六处对比度策展排除(luxe/campaign/
2803
+ * classroom 的 content 排除 banner-heading、tech 的 cover/content、
2804
+ * consulting 的 chapter)已在 W4 fix round 随对比度自适应 ink helper
2805
+ * (`src/svg/ink.ts`)的根因修复全部撤销。fix round 自身新发现的两处
2806
+ * (classroom/heritage 的 chapter 排除 fashion-chapter)也已在
2807
+ * post-v0.3 W8 fix round 随 `readableOn` 两墨实测对比度取优的根因修复一并
2808
+ * 撤销(backlog item 2)——十三主题四页型现在均为不折不扣的全集,无任何
2809
+ * 排除残留。页型空集 = 该页型回落调用侧兜底(十三主题四页型均非空,
2810
+ * `definitions.test.ts` 锁死)。id 是通用 string(不再按页型区分
2811
+ * layout id 联合类型)。
2812
+ */
2813
+ layouts: Record<Slide["type"], readonly string[]>;
2814
+ /** Motif:单值,非 allowed-set(spec §3 示意)。undefined = 该主题无 motif 装饰(十三主题中 runway 留空,其余均已设)。 */
2815
+ motif?: MotifId;
2816
+ /**
2817
+ * A theme's own structural personality (theme-structure wave, task T1 —
2818
+ * `.issues/2026-07-26-theme-structure/plan.md`'s 控制器设计裁定 2): per
2819
+ * page type, the layout ids this theme's author wants `resolveLayoutId`
2820
+ * (`src/svg/layout-selection.ts`) to lean toward. Shape mirrors
2821
+ * `StrategyDefinition.layoutTendencies` (`@/narrative`) — the same "named
2822
+ * ids get a soft weight bump, everyone else stays at the floor" contract —
2823
+ * but declared **per slide type** rather than content-only: a strategy's
2824
+ * `layoutTendencies` is content-only, so on cover/chapter/ending a theme
2825
+ * competes only with `StrategyDefinition.identityTendencies` (which
2826
+ * `tendencyIdsFor` does consult for those three types — an earlier draft
2827
+ * of this comment wrongly claimed no strategy signal reached them at all).
2828
+ * **Consequence worth knowing when declaring:** because `weightOf`
2829
+ * composes via `Math.max`, a theme tendency naming an id the active
2830
+ * strategy's `identityTendencies` already names adds no differential pull
2831
+ * for that id under that strategy (max(3,3) = 3) — a theme's structural
2832
+ * character therefore reads most clearly on ids the strategies do not
2833
+ * already favor. Content can carry both a strategy tendency
2834
+ * and a theme tendency at once; `weightOf` composes every live layer via
2835
+ * `Math.max`, never multiplication (same ruling `BEAT_TENDENCY_WEIGHT`'s
2836
+ * doc comment already argues for: agreement between layers corroborates
2837
+ * the same preference dimension, it does not square the pull).
2838
+ *
2839
+ * **Soft weight, not a whitelist — `layouts` above stays the one hard
2840
+ * boundary.** A slide type's candidate pool is built from `layouts[slideType]`
2841
+ * *before* any tendency is ever consulted (`resolveLayoutId`'s own
2842
+ * `pool` construction), so an id this record names for a page type it is
2843
+ * not also present in that same page type's `layouts` set can never be
2844
+ * scored — it is invisible to `weightOf`, not merely down-weighted. That
2845
+ * silent no-op is exactly why it counts as a theme-author mistake rather
2846
+ * than a legal (if unusual) declaration — `definitions.test.ts`'s
2847
+ * consistency sweep over the 13 builtins, and `registerTheme`'s own
2848
+ * validation below for any future custom theme, both fail loudly the
2849
+ * moment a `layoutTendencies` entry names an id outside its own page
2850
+ * type's `layouts` set, so the mistake surfaces at registration/test time
2851
+ * instead of silently doing nothing at render time.
2852
+ *
2853
+ * Optional at every level (the whole field, and independently each of its
2854
+ * four page-type entries) — **omission is not a lesser default, it is
2855
+ * today's exact behavior**: a page type this record doesn't cover (key
2856
+ * absent, or the field itself `undefined`) contributes a uniform weight of
2857
+ * 1 to every candidate, the same "no theme-layer opinion" no-op floor
2858
+ * `beatTendencies === undefined` already gives beat. None of the 13
2859
+ * builtins declare this field yet (theme-structure wave task T1 is the
2860
+ * mechanism only — task T2 is where individual builtins pick up a
2861
+ * personality), so every one of them renders byte-identically to before
2862
+ * this field existed.
2863
+ */
2864
+ layoutTendencies?: Partial<Record<Slide["type"], readonly string[]>>;
2865
+ /**
2866
+ * Which sparse climax pins this theme is willing to honour. This is not a
2867
+ * curated auto-pick pool: pinOnly sparse ids never enter `layouts[slideType]`
2868
+ * (`fullLayoutSet` / `excludePinOnly` already drop them), so a list here
2869
+ * does not make `resolveLayoutId` sample them. It is the offer table for
2870
+ * an explicit `slide.layout` pin — the only road that can ever reach a
2871
+ * sparse page.
2872
+ *
2873
+ * **Three shapes, none of them a defaulted array:**
2874
+ * - omitted / `undefined`: this theme offers every id in
2875
+ * {@link SPARSE_LAYOUT_IDS}. Builtins that have not boarded a face still
2876
+ * render the generic content face (`sparseFace` miss → `content-*.tsx`).
2877
+ * Custom themes registered via {@link registerTheme} get the same
2878
+ * omitted-means-all contract; do not default the field to `[]` or the
2879
+ * six-id list on the way in (`getThemeDefinition` round-trips the
2880
+ * registration object).
2881
+ * - `[]`: this theme offers none. An explicit pin of any sparse id warns
2882
+ * at `validateIr` (`ok` stays true) and render falls back to auto-pick
2883
+ * from the ordinary content (or chapter, for `verse-chapter`) pool.
2884
+ * - a list: only those ids. A listed id must be one of the six sparse
2885
+ * ids (`registerTheme` throws {@link PptwiseError} otherwise). It does
2886
+ * **not** have to sit in `layouts[slideType]` — those pools exclude
2887
+ * pinOnly members by construction.
2888
+ *
2889
+ * {@link themeOffersSparse} is the only offer check. Renderers must not
2890
+ * branch on theme id. A pin this theme does not offer is stripped
2891
+ * (`effectiveRequestedLayout`) *before* `resolveLayoutId`'s pin
2892
+ * short-circuit, so fallback reuses the existing auto-pick path instead
2893
+ * of teaching selection about this table.
2894
+ */
2895
+ sparseLayouts?: readonly string[];
2896
+ }
2897
+ /**
2898
+ * `registerTheme`'s input shape (W4, spec §3 "缺省 = 全集"): identical to
2899
+ * {@link ThemeDefinition} except `layouts` is optional, and — when present —
2900
+ * each of its four slide-type entries is independently optional too. A
2901
+ * slide type this theme doesn't narrow (its own key omitted, or the whole
2902
+ * `layouts` object omitted) defaults to that type's full registered-
2903
+ * layout set ({@link FULL_LAYOUTS}) — the exact same default every
2904
+ * builtin theme in `LAYOUTS` above resolves to for a slide type it doesn't
2905
+ * curate away from. `getThemeDefinition`/`REGISTERED_THEMES` still only ever
2906
+ * hold the fully-resolved `ThemeDefinition` shape (`layouts` total over all
2907
+ * four types) — `registerTheme` performs the defaulting once, here, so
2908
+ * every downstream reader (`resolveLayoutId` foremost) can keep assuming
2909
+ * a total record and never re-derive "was this slide type curated or
2910
+ * defaulted".
2911
+ */
2912
+ type ThemeRegistration = Omit<ThemeDefinition, "layouts"> & {
2913
+ layouts?: Partial<Record<Slide["type"], readonly string[]>>;
2914
+ };
2915
+ /**
2916
+ * Register a theme at runtime (SDK seam, not the v0.4 distribution
2917
+ * protocol). Validates just enough to keep the render chain from silently
2918
+ * breaking on a malformed registration — not a full schema:
2919
+ *
2920
+ * - `id` must not collide with a builtin or an already-registered theme.
2921
+ * - each of the four slide types, once defaulted ({@link ThemeRegistration}),
2922
+ * must have at least one layout id that is both registered in
2923
+ * `LAYOUT_REGISTRY` and valid for that slide type (the same registry
2924
+ * `resolveLayoutId`/`FullSlideSvg` select from — a theme never ships
2925
+ * new render code, only a curated subset of the existing 30 layouts +
2926
+ * 4 takeovers, per `docs/architecture.md`'s "Adding a theme" section). An
2927
+ * *explicit* empty array for a slide type still fails this check (the
2928
+ * default only kicks in when the key — or `layouts` itself — is omitted
2929
+ * entirely, `undefined`, never for a caller-supplied `[]`).
2930
+ * - `style` must be present (a JS caller can bypass the TS type).
2931
+ * - `style.colors.text`/`style.colors.muted` must each clear the
2932
+ * {@link CONTRAST_FLOOR} against a {@link CONTRAST_CHECKED_SLIDE_TYPES}
2933
+ * slide type's own resolved default background — see
2934
+ * {@link assertContrastFloor}'s own doc comment.
2935
+ * - `sparseLayouts`, when present, may be empty (offers none) or a list of
2936
+ * {@link SPARSE_LAYOUT_IDS} members. A listed non-sparse id throws. The
2937
+ * field is not defaulted when omitted (`undefined` = offer all six).
2938
+ *
2939
+ * Also `console.warn`s (never throws) once for each of `style.fonts.heading`/
2940
+ * `style.fonts.body` that resolves to a face with no exact width table — see
2941
+ * {@link warnUnmeasuredFace}'s own doc comment. Fires only for a
2942
+ * registration that clears every check above (i.e. one that is actually
2943
+ * about to succeed).
2944
+ *
2945
+ * Once registered, the theme participates in `getInstalledThemeIds`,
2946
+ * `getThemeDefinition` (hence `layout-selection.ts`/`FullSlideSvg`'s
2947
+ * selection and `resolveBrand`), and `themes/index.ts`'s `resolveStyle` —
2948
+ * every internal theme lookup, with no separate "registered theme" branch
2949
+ * for callers to remember.
2950
+ */
2951
+ declare function registerTheme(def: ThemeRegistration): void;
2952
+ /** Every installed theme id: the 13 builtins, then registered themes in registration order. */
2953
+ declare function getInstalledThemeIds(): readonly string[];
2954
+ /**
2955
+ * Resolve a theme id to its full definition — a registered theme first, then
2956
+ * the builtin fallback (`THEME_DEFINITIONS[resolveThemeId(id)]`, which itself
2957
+ * folds an unrecognized id to consulting). The one lookup every internal
2958
+ * consumer that used to read `THEME_DEFINITIONS[resolveThemeId(id)]`
2959
+ * directly (`layout-selection.ts`, `full-slide-svg.tsx`) now calls instead, so
2960
+ * a registered theme's curated layouts actually drive selection end-to-end.
2961
+ */
2962
+ declare function getThemeDefinition(id: string): ThemeDefinition;
2963
+
2964
+ /** The theme-file JSON shape written by `pptwise brand extract` / SDK
2965
+ * {@link extractBrandTheme}, and read back by `--theme-file` /
2966
+ * `brand-theme-file.ts`'s `registerBrandThemeFile`. Pure data (裁定 3): no
2967
+ * `layouts`/`motif`/`layoutTendencies` — those default to the full set,
2968
+ * same as any other `registerTheme` caller that omits them. `label` is
2969
+ * informational only (a human-readable name for the CLI/UI to show); the
2970
+ * render chain never reads it — the theme's own `id` is what everything
2971
+ * else keys off. */
2972
+ interface BrandThemeFile {
2973
+ id: string;
2974
+ label: string;
2975
+ style: StyleTokens;
2976
+ brand: BrandConfig;
2977
+ tags: string[];
2978
+ }
2979
+ interface ExtractBrandThemeOptions {
2980
+ /** Theme id — defaults to a slug of {@link ExtractBrandThemeOptions.label},
2981
+ * or (when that's also absent) a slug of the source theme part's own
2982
+ * `<a:clrScheme name="…">`. The CLI (`pptwise brand extract`) always
2983
+ * passes one explicitly (`--id`, or a slug of the `-o` filename — 裁定
2984
+ * 4) — this default only ever fires for a bare SDK caller. */
2985
+ id?: string;
2986
+ /** Human-readable label — defaults to the source theme part's own
2987
+ * `<a:clrScheme name="…">`. */
2988
+ label?: string;
2989
+ }
2990
+ /**
2991
+ * Extract a {@link BrandThemeFile} from raw OOXML package bytes (a
2992
+ * `.thmx`/`.potx`/`.pptx` file's own bytes — this function only ever reads
2993
+ * the theme part inside, nothing else in the package). Deterministic: the
2994
+ * same bytes always produce the same output (no randomness, no wall-clock
2995
+ * read) — required for this wave's fixture tests' double-run-equality
2996
+ * assertions.
2997
+ *
2998
+ * Throws {@link PptwiseError} for two *structural* failures — data this
2999
+ * function has no reasonable default for:
3000
+ * - no theme part found in the package at all
3001
+ * - a theme part with no `dk1`/`lt1` (nothing to derive `bg`/`text` from) or
3002
+ * no accent color at all (nothing to derive `primary`/`chartPalette` from)
3003
+ *
3004
+ * Everything else degrades gracefully rather than throwing — a missing
3005
+ * `lt2` falls back to `bg` (no distinct surface tone available), a missing
3006
+ * `accent1` falls back to `dk2` then to the first available chart color, a
3007
+ * missing font falls back to a Windows-safe generic stack — so a real-world
3008
+ * theme missing a slot or two (this wave's own fixture matrix covers
3009
+ * several) still produces a usable, if plainer, theme. A palette that
3010
+ * degrades all the way into unreadable territory (near-identical
3011
+ * text/bg/muted tones) is *not* rejected here — that's `registerTheme`'s
3012
+ * own `assertContrastFloor` (`./definitions.ts`) job, at load time, with a
3013
+ * message naming the actual failing token/ratio (see `brand-theme-file.ts`).
3014
+ */
3015
+ declare function extractBrandTheme(bytes: Uint8Array | ArrayBuffer, opts?: ExtractBrandThemeOptions): Promise<BrandThemeFile>;
3016
+
3017
+ /**
3018
+ * Load an on-disk {@link BrandThemeFile} (`./brand-extract.ts`'s output, or
3019
+ * a hand-authored equivalent) into the theme registry (brand-extract wave,
3020
+ * 裁定 3: "装载走 registerTheme"). Split from `brand-extract.ts` because this
3021
+ * module's job is untrusted-input validation + registration — a theme file
3022
+ * is user-editable JSON, not something `extractBrandTheme`'s own return
3023
+ * value needs re-validated against when it's the one producing it — while
3024
+ * that module's job is the pure OOXML→tokens derivation. Both stay
3025
+ * browser-safe (no Node-only import), same as every other file under
3026
+ * `src/themes/` (`AGENTS.md`'s dependency-closure rule): the actual file
3027
+ * read (`readFile`) happens in `src/cli/commands.ts`, which hands this
3028
+ * module already-parsed JSON.
3029
+ */
3030
+
3031
+ declare const BrandThemeFileSchema: z.ZodObject<{
3032
+ id: z.ZodString;
3033
+ label: z.ZodOptional<z.ZodString>;
3034
+ style: z.ZodObject<{
3035
+ id: z.ZodString;
3036
+ allowCustomBackground: z.ZodOptional<z.ZodBoolean>;
3037
+ colors: z.ZodObject<{
3038
+ bg: z.ZodString;
3039
+ surface: z.ZodString;
3040
+ panel: z.ZodOptional<z.ZodString>;
3041
+ primary: z.ZodString;
3042
+ accent: z.ZodString;
3043
+ text: z.ZodString;
3044
+ muted: z.ZodString;
3045
+ border: z.ZodOptional<z.ZodString>;
3046
+ chartPalette: z.ZodArray<z.ZodString>;
3047
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
3048
+ cardStroke: z.ZodOptional<z.ZodString>;
3049
+ }, z.core.$strict>;
3050
+ fonts: z.ZodObject<{
3051
+ heading: z.ZodArray<z.ZodString>;
3052
+ body: z.ZodArray<z.ZodString>;
3053
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
3054
+ }, z.core.$strict>;
3055
+ shape: z.ZodOptional<z.ZodObject<{
3056
+ radius: z.ZodOptional<z.ZodNumber>;
3057
+ gapScale: z.ZodOptional<z.ZodNumber>;
3058
+ typeScale: z.ZodOptional<z.ZodNumber>;
3059
+ }, z.core.$strict>>;
3060
+ defaultBackgrounds: z.ZodObject<{
3061
+ cover: z.ZodDiscriminatedUnion<[z.ZodObject<{
3062
+ kind: z.ZodLiteral<"color">;
3063
+ value: z.ZodString;
3064
+ }, z.core.$strict>, z.ZodObject<{
3065
+ kind: z.ZodLiteral<"gradient">;
3066
+ from: z.ZodString;
3067
+ to: z.ZodString;
3068
+ direction: z.ZodOptional<z.ZodEnum<{
3069
+ tb: "tb";
3070
+ lr: "lr";
3071
+ diagonal: "diagonal";
3072
+ }>>;
3073
+ }, z.core.$strict>, z.ZodObject<{
3074
+ kind: z.ZodLiteral<"asset">;
3075
+ asset_id: z.ZodString;
3076
+ overlay: z.ZodOptional<z.ZodObject<{
3077
+ color: z.ZodString;
3078
+ opacity: z.ZodNumber;
3079
+ }, z.core.$strict>>;
3080
+ fit: z.ZodOptional<z.ZodEnum<{
3081
+ contain: "contain";
3082
+ cover: "cover";
3083
+ }>>;
3084
+ }, z.core.$strict>], "kind">;
3085
+ chapter: z.ZodDiscriminatedUnion<[z.ZodObject<{
3086
+ kind: z.ZodLiteral<"color">;
3087
+ value: z.ZodString;
3088
+ }, z.core.$strict>, z.ZodObject<{
3089
+ kind: z.ZodLiteral<"gradient">;
3090
+ from: z.ZodString;
3091
+ to: z.ZodString;
3092
+ direction: z.ZodOptional<z.ZodEnum<{
3093
+ tb: "tb";
3094
+ lr: "lr";
3095
+ diagonal: "diagonal";
3096
+ }>>;
3097
+ }, z.core.$strict>, z.ZodObject<{
3098
+ kind: z.ZodLiteral<"asset">;
3099
+ asset_id: z.ZodString;
3100
+ overlay: z.ZodOptional<z.ZodObject<{
3101
+ color: z.ZodString;
3102
+ opacity: z.ZodNumber;
3103
+ }, z.core.$strict>>;
3104
+ fit: z.ZodOptional<z.ZodEnum<{
3105
+ contain: "contain";
3106
+ cover: "cover";
3107
+ }>>;
3108
+ }, z.core.$strict>], "kind">;
3109
+ content: z.ZodDiscriminatedUnion<[z.ZodObject<{
3110
+ kind: z.ZodLiteral<"color">;
3111
+ value: z.ZodString;
3112
+ }, z.core.$strict>, z.ZodObject<{
3113
+ kind: z.ZodLiteral<"gradient">;
3114
+ from: z.ZodString;
3115
+ to: z.ZodString;
3116
+ direction: z.ZodOptional<z.ZodEnum<{
3117
+ tb: "tb";
3118
+ lr: "lr";
3119
+ diagonal: "diagonal";
3120
+ }>>;
3121
+ }, z.core.$strict>, z.ZodObject<{
3122
+ kind: z.ZodLiteral<"asset">;
3123
+ asset_id: z.ZodString;
3124
+ overlay: z.ZodOptional<z.ZodObject<{
3125
+ color: z.ZodString;
3126
+ opacity: z.ZodNumber;
3127
+ }, z.core.$strict>>;
3128
+ fit: z.ZodOptional<z.ZodEnum<{
3129
+ contain: "contain";
3130
+ cover: "cover";
3131
+ }>>;
3132
+ }, z.core.$strict>], "kind">;
3133
+ ending: z.ZodDiscriminatedUnion<[z.ZodObject<{
3134
+ kind: z.ZodLiteral<"color">;
3135
+ value: z.ZodString;
3136
+ }, z.core.$strict>, z.ZodObject<{
3137
+ kind: z.ZodLiteral<"gradient">;
3138
+ from: z.ZodString;
3139
+ to: z.ZodString;
3140
+ direction: z.ZodOptional<z.ZodEnum<{
3141
+ tb: "tb";
3142
+ lr: "lr";
3143
+ diagonal: "diagonal";
3144
+ }>>;
3145
+ }, z.core.$strict>, z.ZodObject<{
3146
+ kind: z.ZodLiteral<"asset">;
3147
+ asset_id: z.ZodString;
3148
+ overlay: z.ZodOptional<z.ZodObject<{
3149
+ color: z.ZodString;
3150
+ opacity: z.ZodNumber;
3151
+ }, z.core.$strict>>;
3152
+ fit: z.ZodOptional<z.ZodEnum<{
3153
+ contain: "contain";
3154
+ cover: "cover";
3155
+ }>>;
3156
+ }, z.core.$strict>], "kind">;
3157
+ }, z.core.$strict>;
3158
+ }, z.core.$strict>;
3159
+ brand: z.ZodOptional<z.ZodObject<{
3160
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
3161
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
3162
+ suppressFooterMeta: z.ZodOptional<z.ZodBoolean>;
3163
+ }, z.core.$strict>>;
3164
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
3165
+ }, z.core.$strict>;
3166
+ /** Parse+validate raw JSON (already `JSON.parse`d by the caller — see
3167
+ * `src/cli/load-ir.ts`'s `loadIrFile`) as a {@link BrandThemeFile}. `source`
3168
+ * names the file in the thrown message (a path, or another caller-chosen
3169
+ * label) — mirrors `loadStyleFile`'s (`src/cli/commands.ts`) own
3170
+ * "invalid <kind> file <path>:\n<detail>" shape so a theme-file error reads
3171
+ * exactly like every other config-file validation error in this CLI. */
3172
+ declare function parseBrandThemeFile(raw: unknown, source: string): BrandThemeFile;
3173
+ /**
3174
+ * Register a parsed {@link BrandThemeFile} (`registerTheme`, `./definitions.ts`
3175
+ * — 裁定 3: this is the *only* loading path, so its contrast floor
3176
+ * automatically gates every custom theme). Two collision outcomes, per 裁定
3177
+ * 4:
3178
+ *
3179
+ * - `file.id` names a builtin (`CANONICAL_THEME_IDS`) → always a hard error,
3180
+ * regardless of whether this exact file was already loaded once — a
3181
+ * custom theme must never shadow a builtin id (determinism: which
3182
+ * `layouts`/`layoutTendencies` "consulting" resolves to must never depend
3183
+ * on whether some unrelated `--theme-file` ran first).
3184
+ * - `file.id` is already registered (not a builtin) → a silent no-op, not a
3185
+ * second error. `registerTheme` itself throws unconditionally on *any*
3186
+ * id collision, builtin or not — this idempotent skip exists because
3187
+ * `pptwise serve`'s rebuild loop (`src/cli/serve.ts`) calls this on every
3188
+ * file-watch rebuild for the *same* deck, and the theme-file/deck-dir
3189
+ * `theme.json` it re-reads each time resolves to the same id every time.
3190
+ * (A theme file edited *between* two rebuilds keeps whichever version
3191
+ * registered first for that process's lifetime — no live-reload for a
3192
+ * theme file's own content, only for deck content; out of this wave's
3193
+ * scope.)
3194
+ *
3195
+ * Returns `file.id` so a caller can fold it into the deck's own
3196
+ * theme-resolution precedence chain (`applyDeckConfig`, `src/cli/commands.ts`).
3197
+ */
3198
+ declare function registerBrandThemeFile(file: BrandThemeFile): string;
3199
+
3200
+ /**
3201
+ * Narrative argument style (spec §5's five-way strategy classification,
3202
+ * renamed from "mode" — spec §8.1). Each strategy carries a
3203
+ * component/layout tendency set (soft-weight material, consumed by W4's
3204
+ * weighted selection — nothing consumes it this wave) and a beat policy
3205
+ * (renamed from "rhythm policy" — spec §2.3's deck/page beat split; consumed
3206
+ * by W5's spec-validate rotation gate).
3207
+ */
3208
+ type Strategy = (typeof STRATEGY_VALUES)[number];
3209
+ /**
3210
+ * Content density budget + typographic baseline (spec §5 pacing table,
3211
+ * renamed from "delivery" — spec §8.1). See {@link PacingBudget} for the
3212
+ * dual-attribute capacity split this axis is one half of.
3213
+ */
3214
+ type Pacing = (typeof PACING_VALUES)[number];
3215
+ /**
3216
+ * Tone anchor only (spec §5: audience is tone-anchoring only, no rendering
3217
+ * effect on the IR yet). Reserved for a future lint pass (e.g. executive ×
3218
+ * long paragraphs → suggest kpi_cards/verdict_banner instead). The rule set
3219
+ * itself is explicitly out of scope this wave (spec §10 open questions).
3220
+ * Name unchanged by the vocabulary-v4 rename (spec §4.3: "audience 保持不变").
3221
+ */
3222
+ type Audience = (typeof AUDIENCE_VALUES)[number];
3223
+ interface NarrativeProfile {
3224
+ readonly strategy: Strategy;
3225
+ readonly pacing: Pacing;
3226
+ readonly audience: Audience;
3227
+ }
3228
+
3229
+ interface StrategyDefinition {
3230
+ id: Strategy;
3231
+ /**
3232
+ * Layout/component tendency set (soft-weight material). Filled row-for-row
3233
+ * from spec §5's strategy table for W4's weighted selection step (spec §6
3234
+ * step 4: in-set candidates get ×3 weight, out-of-set ×1 floor — not
3235
+ * implemented yet, this module only stores the data).
3236
+ *
3237
+ * The set deliberately mixes two different vocabularies: component `type`
3238
+ * names (e.g. "kpi_cards", "chart" — see the `Component` discriminated
3239
+ * union in `ir/index.ts`) and layout `id`s (e.g. "image-split" — see
3240
+ * `LAYOUT_REGISTRY` in `svg/layouts/registry.ts`). This is intentional,
3241
+ * not an oversight: W4's weighting step resolves each entry against
3242
+ * whichever vocabulary it belongs to (component types when scoring a
3243
+ * candidate's components, layout ids when scoring the candidate layout
3244
+ * itself). Nothing in this wave (W3) reads this field.
3245
+ */
3246
+ tendencies: readonly string[];
3247
+ /**
3248
+ * Content-layout soft-weight set for W4's weighted layout selection
3249
+ * (spec §6 step 4: in-set candidates get `TENDENCY_WEIGHT` (×3), out-of-set
3250
+ * get the `BASE_WEIGHT` floor (×1) — both named constants live in
3251
+ * `svg/layout-selection.ts`, next to `resolveLayoutId`, the sole
3252
+ * consumer). Deliberately a separate field from {@link tendencies} above,
3253
+ * not a reinterpretation of it: `tendencies` mixes component-type names and
3254
+ * layout ids drawn from spec §5's strategy table verbatim and also feeds W5's
3255
+ * spec `focus` vocabulary gate, so narrowing its meaning here would be a
3256
+ * breaking change to an existing consumer. This field holds only
3257
+ * `LAYOUT_REGISTRY` content-layout ids (`svg/layouts/registry.ts`'s
3258
+ * `CONTENT_LAYOUT_DEFS` keys) — cover/chapter/ending ids never appear in this
3259
+ * field's list, `resolveLayoutId` reads {@link identityTendencies}
3260
+ * below for those three slide types instead, so the two fields' id
3261
+ * namespaces stay disjoint by construction rather than by convention.
3262
+ * `tone-adaptive-content` appears in no strategy's list here (spec's "万金
3263
+ * 油" call-out: it is the one content layout meant to read as
3264
+ * strategy-neutral, so it always gets the ×1 floor).
3265
+ *
3266
+ * **`image-lead-split` (content-layout expansion wave, task T3) joins
3267
+ * it, deliberately, as this field's third permanently-neutral member** — a
3268
+ * decision, not an oversight (T1's own review flagged its absence here as
3269
+ * looking accidental, so this note exists to close that off explicitly).
3270
+ * Its defining trait is an *asset-availability* choice, not an
3271
+ * *argument-style* one: the unconditional visual/text split depends on
3272
+ * whether the deck's author actually has a chart/photo worth leading with
3273
+ * (see `content-image-lead-split.tsx`'s own file header) — a pyramid deck,
3274
+ * a storytelling deck, a showcase deck, and a briefing deck can equally
3275
+ * have (or lack) a strong image to lead with, so no single strategy's
3276
+ * rhetorical register (MECE evidence-first, editorial restraint,
3277
+ * step-by-step procedure, glossy visual punch, plain scannability) owns
3278
+ * this composition more than another's. **Reconsidered specifically
3279
+ * against `showcase`** (the wave's final review, see `split-band`'s own
3280
+ * entry in `showcase.layoutTendencies` below): showcase's register is
3281
+ * visual *punch* (bold color blocks, opaque highlight panels), not visual
3282
+ * *presence* — having an image at all doesn't say anything about how
3283
+ * loudly the page asserts itself, so this layout's asset-dependence
3284
+ * still doesn't map onto showcase's own membership criterion the way
3285
+ * `split-band`'s full-bleed color band does. It still went back to
3286
+ * neutral, not out of inertia. It *does* carry a beat-level personality
3287
+ * instead (`BEAT_TENDENCIES`, `svg/layout-selection.ts` — `breathing`) —
3288
+ * that axis measures page-level density character, a question this
3289
+ * field's argument-style axis doesn't ask.
3290
+ *
3291
+ * **`split-band` (same wave, same task) does NOT join this field** — it
3292
+ * was originally proposed here on the same "frame, not argument" theory
3293
+ * above, but the wave's final review refuted that specific claim with
3294
+ * in-file evidence: `split-band`'s defining feature, a full-bleed
3295
+ * (x=0, w=1280) `colors.primary` header band, is the "bold opaque color
3296
+ * block" idiom that earns `showcase.layoutTendencies` membership (the
3297
+ * retired `side-highlight` panel used to share that register), and
3298
+ * showcase's identity picks (`fashion-masthead`/`fashion-chapter`/
3299
+ * `fashion-ending`) are themselves built from full-bleed color blocks —
3300
+ * visual weight *is* showcase's rhetorical register, not a
3301
+ * register-neutral frame choice. `split-band` is listed in
3302
+ * `showcase.layoutTendencies` below instead of here.
3303
+ *
3304
+ * **Historical note (corrected P1 variety wave, task 3):** this field's
3305
+ * doc comment used to claim cover/chapter/ending stayed uniformly sampled
3306
+ * because "身份页个性来自 theme 不来自 strategy" — that claim was already
3307
+ * false when written (`theme.layouts` only curates the candidate *pool*,
3308
+ * spec §6 step 3 — the 13 built-in themes' pools are all the full registry
3309
+ * set for every slide type, so `theme.id` contributes zero shaping either —
3310
+ * see `.issues/.../dr/c-diversity.md`'s §1.1 measurement). Identity pages
3311
+ * now get their own strategy-driven soft weighting via
3312
+ * {@link identityTendencies} — theme's role stays exactly what step 3
3313
+ * always said it was — pool curation, not per-layout preference — for
3314
+ * both content and identity slides alike.
3315
+ */
3316
+ layoutTendencies: readonly string[];
3317
+ /**
3318
+ * Cover/chapter/ending soft-weight sets (P1 variety wave, task 3 — "身份页
3319
+ * strategy 软加权"), one per identity slide type. Same ×3/×1 mechanics as
3320
+ * {@link layoutTendencies} (`TENDENCY_WEIGHT`/`BASE_WEIGHT`,
3321
+ * `svg/layout-selection.ts`) and the same consumer (`resolveLayoutId`),
3322
+ * just scoped to a disjoint id namespace: this field holds only
3323
+ * `LAYOUT_REGISTRY` cover/chapter/ending layout ids
3324
+ * (`svg/layouts/registry.ts`'s `COVER_LAYOUT_DEFS`/`CHAPTER_LAYOUT_DEFS`/
3325
+ * `ENDING_LAYOUT_DEFS` keys), never a content id — `resolveLayoutId` picks
3326
+ * this field or {@link layoutTendencies} based on the slide type being
3327
+ * resolved, never both for the same candidate.
3328
+ *
3329
+ * Deliberately small (2-3 members per page type, out of a 7-8-id pool) —
3330
+ * spec: "映射表提案是内容决策...权重不是排除", so every non-member layout
3331
+ * still stays reachable at the `BASE_WEIGHT` floor, just less often. Each
3332
+ * member's rationale is documented next to its own strategy entry below,
3333
+ * grounded in that layout's own body comment in
3334
+ * `svg/layouts/registry.ts` (`COVER_LAYOUT_DEFS`/`CHAPTER_LAYOUT_DEFS`/
3335
+ * `ENDING_LAYOUT_DEFS`), not its name alone.
3336
+ *
3337
+ * `tone-adaptive-header`/`tone-adaptive-chapter`/`tone-adaptive-ending`
3338
+ * never appear in any strategy's set here, mirroring
3339
+ * `tone-adaptive-content`'s absence from every {@link layoutTendencies}
3340
+ * list: each is its page type's "万金油" (registry.ts's own convention),
3341
+ * the one layout each identity slide type is meant to keep reading as
3342
+ * strategy-neutral.
3343
+ *
3344
+ * Supersedes the old design note this field replaces (formerly recorded
3345
+ * on {@link layoutTendencies} below, now corrected there): identity pages
3346
+ * were never actually reading `theme.id` for their character either
3347
+ * (`theme.layouts` only curates the *pool*, spec §6 step 3 — it was never
3348
+ * a per-layout weighting signal, for content or identity slides alike)
3349
+ * — this field is what now gives cover/chapter/ending a strategy-driven
3350
+ * personality, the same soft-weight mechanism content pages have had since
3351
+ * W4.
3352
+ */
3353
+ identityTendencies: {
3354
+ readonly cover: readonly string[];
3355
+ readonly chapter: readonly string[];
3356
+ readonly ending: readonly string[];
3357
+ };
3358
+ /**
3359
+ * Beat template descriptor (spec §5's per-strategy beat-default column,
3360
+ * renamed from "rhythm" — spec §2.3), parameterized by strategy for W5's
3361
+ * spec-validate rotation gate — e.g. briefing is exempt from a generic
3362
+ * "three same-beat pages in a row is an error" rule because uniform-dense
3363
+ * *is* briefing's correct default, not a violation of it (spec §5's
3364
+ * spec-gate section calls out that a generic same-beat-streak rule would
3365
+ * reject briefing's own default). Not consumed this wave.
3366
+ */
3367
+ beatPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
3368
+ }
3369
+ declare const STRATEGY_DEFINITIONS: Record<Strategy, StrategyDefinition>;
3370
+
3371
+ interface PacingBudget {
3372
+ /**
3373
+ * Body-text baseline, in px, at 1280×720 slide geometry (spec §5 pacing
3374
+ * table's body-baseline column). Wired into rendering as of W4 task 3
3375
+ * (design decision 9): `src/svg/full-slide-svg.tsx` resolves
3376
+ * `PACING_BUDGETS[resolveNarrative(ir.narrative).pacing].bodyBaselinePx`
3377
+ * once and passes it into `buildCtx`, which stores it as
3378
+ * `ComponentCtx.bodyFontPx` — the sole font-size input for the
3379
+ * paragraph/bullets/callout trio ("正文" = continuous running text).
3380
+ * Every other component's own bespoke type scale, the heading system, and
3381
+ * quote's fixed 26px attribution line don't read this field. Since
3382
+ * `balanced` (24px) is the narrative default, an omitted-narrative deck
3383
+ * now renders body text at 24px, not the previous fixed 20px — the
3384
+ * one-time, spec-sanctioned snapshot re-pin that landed alongside this
3385
+ * wiring (W4 task 3, unchanged since — vocabulary-v4 only renames this
3386
+ * table, it doesn't touch any of its numbers, spec §10).
3387
+ */
3388
+ bodyBaselinePx: number;
3389
+ /**
3390
+ * Per-slide editorial budget (component count) — content discipline
3391
+ * ("how many things belong on this slide"), not geometry. Spec §5's
3392
+ * dual-attribute capacity split keeps *physical* capacity ("how many
3393
+ * things fit in this layout's slots") on the layout registry's body-slot
3394
+ * `capacity` metadata (`svg/layouts/registry.ts`). The W3 quality gate
3395
+ * takes `min(this budget, the resolved layout's body capacity)`.
3396
+ */
3397
+ maxComponentsPerSlide: number;
3398
+ bullets: {
3399
+ maxItems: number;
3400
+ /**
3401
+ * Same "unit" concept the deleted `CAPACITY.bullets` used
3402
+ * (`measureTextUnits`, CJK weight = 1.0) — a visual-width-weighted
3403
+ * character count, not a raw `.length`. The old physical ceiling (53,
3404
+ * derived from render geometry) was deleted in W3 without a replacement
3405
+ * check because every pacing's editorial budget here (30/40/48) is
3406
+ * already strictly tighter than it — the quality gate now applies
3407
+ * exactly this one number per pacing.
3408
+ */
3409
+ maxUnitsPerItem: number;
3410
+ };
3411
+ }
3412
+ /**
3413
+ * Pinned to spec §5's pacing table (`bodyBaselinePx` / editorial budget /
3414
+ * bullets budget columns): dense 20/5/6×48, balanced 24/4/5×40, spacious
3415
+ * 32/3/4×30 — same three rows as the pre-rename `text`/`balanced`/
3416
+ * `presentation` delivery table, values byte-for-byte unchanged (spec §10).
3417
+ */
3418
+ declare const PACING_BUDGETS: Record<Pacing, PacingBudget>;
3419
+
3420
+ interface NarrativePreset {
3421
+ id: string;
3422
+ axes: NarrativeProfile;
3423
+ /**
3424
+ * Soft theme recommendations — a suggestion, never a hard constraint
3425
+ * (spec §5). Surfaced in workflow step ① so an agent can open with a
3426
+ * themed proposal — the user may still pick any theme. Every entry here
3427
+ * must be a real `BUILTIN_THEME_IDS` member (`ir/index.ts`) — enforced by
3428
+ * this module's test suite, which imports and tests against it.
3429
+ */
3430
+ themeRecommendations: readonly string[];
3431
+ }
3432
+ /**
3433
+ * Pinned to spec §5's 7 named presets and their theme recommendation table.
3434
+ * Preset ids are unchanged by the vocabulary-v4 rename (spec §5: "预设 ID
3435
+ * 保持不变") — only each preset's internal axes keys/values follow the
3436
+ * strategy/pacing rename (e.g. `annual-review`'s `mode: "narrative"` is now
3437
+ * `strategy: "storytelling"`, spec §5's own worked example).
3438
+ */
3439
+ declare const NARRATIVE_PRESETS: Record<string, NarrativePreset>;
3440
+ /**
3441
+ * = `NARRATIVE_PRESETS.general.axes` (briefing × balanced × public) — the
3442
+ * global default when narrative is omitted entirely (spec §5's defaults
3443
+ * chain).
3444
+ */
3445
+ declare const DEFAULT_NARRATIVE: NarrativeProfile;
3446
+ /**
3447
+ * Resolve a narrative input down to concrete axes, per spec §5's design
3448
+ * principle "omission gets the default, a typo is a hard error" (weak-model
3449
+ * friendly: a model that leaves a field out gets a sane deck, a model that
3450
+ * misspells a value gets a loud, actionable error instead of a silently
3451
+ * wrong deck):
3452
+ *
3453
+ * - `undefined` → {@link DEFAULT_NARRATIVE} (the `general` preset's axes)
3454
+ * - a preset id string → that preset's axes (unknown id throws
3455
+ * {@link PptwiseError}, listing the available preset ids)
3456
+ * - a partial axes object → each axis defaults independently
3457
+ * (strategy → "briefing", pacing → "balanced", audience → "public" — these
3458
+ * happen to equal `DEFAULT_NARRATIVE`'s values because `general` *is* that
3459
+ * exact combination, but the fallback here is per-axis, not "any omitted
3460
+ * axis falls back to the whole default object")
3461
+ * - an `{id: <preset>}` object (T0b fix 2, scope-extended — see
3462
+ * {@link rescueIdShape}) → silently treated as that bare preset-id string,
3463
+ * the same weak-model shape slip `theme: {id: "consulting"}` invites by
3464
+ * analogy. Folded directly into this function's own entry (not just a
3465
+ * pre-parse pass two of its six call sites happen to run) so every caller
3466
+ * — `validateIr`, `validateSpec`, `layout-selection.ts`,
3467
+ * `full-slide-svg.tsx`, `ir-quality.ts`, `cli/commands.ts` alike — gets
3468
+ * this tolerance for free, present and future. Silent here (no rewrite
3469
+ * note — this is a pure resolver with no reporting channel, and adding one
3470
+ * would ripple into every one of those call sites' own return-shape
3471
+ * expectations); `validateIr`/`validateSpec` additionally run
3472
+ * {@link normalizeNarrativeShape} on their own raw pre-parse input first,
3473
+ * specifically to report + persist the rewrite into what they return.
3474
+ *
3475
+ * An unknown axis value, or an unknown key on the partial axes object,
3476
+ * always throws {@link PptwiseError} (never silently ignored or dropped) —
3477
+ * omission and a typo are different intents, and only the former has a
3478
+ * reasonable default. A mixed `{id, strategy}`-style shape is exactly this
3479
+ * case ({@link rescueIdShape} declines it): `id` is simply an unrecognized
3480
+ * key, so it hard-errors the same as any other typo.
3481
+ *
3482
+ * Renamed from `resolveScenario` (spec §8.1). Callers that still hold a
3483
+ * pre-rename `mode`/`delivery` shaped input (e.g. a v3 IR's `scenario`
3484
+ * field) must migrate it first — see `migrateIrV3ToV4`
3485
+ * (`src/ir/migrate.ts`) for the deterministic field/value mapping. A
3486
+ * v4-track document that still writes the old field/value spelling gets no
3487
+ * such rescue (spec §16, reversing the now-superseded §15.4): this function
3488
+ * hard-errors on it, same as any other unknown axis key or value. The
3489
+ * `{id}` shape above is not that — it was never a v3 `scenario` shape, just
3490
+ * a new confusion weak models invent by analogy to `theme.id`.
3491
+ */
3492
+ declare function resolveNarrative(input: string | Partial<NarrativeProfile> | undefined): NarrativeProfile;
3493
+
3494
+ /**
3495
+ * Deck spec schema + validation (spec §5 "plan artifact and hard gates", W5
3496
+ * task 2 — renamed to "Deck Spec" per the vocabulary-v4 rename, spec §6/§8.1;
3497
+ * "spec §N" citations throughout this file that predate this rename still
3498
+ * point at that original W5 design doc, not this rename's own spec — left
3499
+ * as historical citations, not renumbered). That rename originally covered
3500
+ * the exported concepts and CLI vocabulary only — this module's own
3501
+ * directory stayed `src/plan` for a while after, a known stale name the
3502
+ * src domain-reorg wave's task T1c closed by moving it to `src/spec`
3503
+ * (mechanical `git mv`, every exported symbol unchanged): the path now
3504
+ * matches the vocabulary this header already used throughout.
3505
+ *
3506
+ * A deck spec is a workflow artifact, not a render prerequisite (spec §5's
3507
+ * "escape hatch": a bare IR v3 renders directly, `spec validate` is a
3508
+ * separate, optional gate for the spec-authoring stage of the six-phase
3509
+ * workflow). This module stays pure and Node-free — no `fs`, no CLI concerns
3510
+ * — so it can sit in `src/index.ts`'s dependency closure exactly like
3511
+ * `src/ir` and `src/narrative` already do (`AGENTS.md`'s layout rule).
3512
+ * `src/cli/commands.ts` owns the one Node-touching wrapper
3513
+ * (`runSpecValidate`: read file, call {@link validateSpec}, format the
3514
+ * result).
3515
+ *
3516
+ * Design mirrors `api.ts`'s `validateIr`/`ValidateResult` throughout
3517
+ * (structural zod pass first, then a sequential chain of hard-gate
3518
+ * categories, each short-circuiting the chain on its own failure — see
3519
+ * {@link validateSpec}'s own comment for why): same overall shape, adapted
3520
+ * for spec pages being keyed by an author-assigned `id` instead of IR's
3521
+ * positional slide index.
3522
+ */
3523
+
3524
+ /**
3525
+ * Mirrors `SlideSchema.type` / `SlideType` (`ir/index.ts`,
3526
+ * `svg/layouts/registry.ts`) exactly. Kept as an independent literal tuple
3527
+ * here (not imported from either) — a page spec's `type` is a 4-value enum
3528
+ * on its own schema, not a re-export of IR's — but the `satisfies` clause
3529
+ * makes any future drift between the two a compile error instead of a
3530
+ * silent mismatch.
3531
+ */
3532
+ declare const PAGE_TYPES: readonly ["cover", "chapter", "content", "ending"];
3533
+ type PageSpecType = (typeof PAGE_TYPES)[number];
3534
+ type PageBeat = (typeof BEAT_VALUES)[number];
3535
+ /**
3536
+ * A single page spec (spec §5, §6). `type`/`heading` are required — unlike
3537
+ * IR's own `SlideSchema` (where both default/omit for weak-model
3538
+ * friendliness), a deck spec is the authoring artifact those fields get
3539
+ * *locked* from at assemble time (W5 task 3), so leaving either implicit
3540
+ * here would defeat the point. `beat`/`focus`/`summary` stay optional per
3541
+ * spec §5's defaults chain ("beat omitted → auto-rotates by page position —
3542
+ * focus/summary/layout/slot can all be omitted").
3543
+ */
3544
+ declare const PageSpecSchema: z.ZodObject<{
3545
+ id: z.ZodString;
3546
+ type: z.ZodEnum<{
3547
+ content: "content";
3548
+ cover: "cover";
3549
+ chapter: "chapter";
3550
+ ending: "ending";
3551
+ }>;
3552
+ heading: z.ZodString;
3553
+ beat: z.ZodOptional<z.ZodEnum<{
3554
+ dense: "dense";
3555
+ anchor: "anchor";
3556
+ breathing: "breathing";
3557
+ }>>;
3558
+ focus: z.ZodOptional<z.ZodString>;
3559
+ summary: z.ZodOptional<z.ZodString>;
3560
+ }, z.core.$strict>;
3561
+ type PageSpec = z.infer<typeof PageSpecSchema>;
3562
+ /**
3563
+ * Top-level deck spec shape (spec §5, §6). `narrative`/`theme` deliberately
3564
+ * have no schema-level `.default(...)` — same reasoning as `PptxIRSchema`'s
3565
+ * own `narrative` field (`ir/index.ts`): the resolved value is never baked
3566
+ * back into the parsed shape, {@link validateSpec} (here) and, later,
3567
+ * assemble (W5 task 3) each resolve it themselves. `seed` is accepted but
3568
+ * entirely unexamined by this module — "not validateSpec's concern" (spec's
3569
+ * own wording): assemble generates and suggests writing one back on first
3570
+ * materialization.
3571
+ *
3572
+ * `version` stays the literal `"1"` (unchanged value) but now carries an
3573
+ * independent Deck Spec versioning scheme (spec §6: "`deck.spec.json` 使用
3574
+ * 独立的 spec 版本 1。它是新工件,不继承 `deck.plan.json` 的版本号语义") —
3575
+ * this "1" is the Deck Spec artifact's own first version, not a continuation
3576
+ * of the old deck-plan artifact's version counter, even though the digit is
3577
+ * the same.
3578
+ */
3579
+ declare const DeckSpecSchema: z.ZodObject<{
3580
+ version: z.ZodDefault<z.ZodLiteral<"1">>;
3581
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
3582
+ theme: z.ZodOptional<z.ZodString>;
3583
+ filename: z.ZodOptional<z.ZodString>;
3584
+ seed: z.ZodOptional<z.ZodNumber>;
3585
+ meta: z.ZodDefault<z.ZodObject<{
3586
+ organization: z.ZodOptional<z.ZodString>;
3587
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
3588
+ name: z.ZodString;
3589
+ role: z.ZodOptional<z.ZodString>;
3590
+ org: z.ZodOptional<z.ZodString>;
3591
+ }, z.core.$strict>>>;
3592
+ date: z.ZodOptional<z.ZodString>;
3593
+ version: z.ZodOptional<z.ZodString>;
3594
+ confidentiality: z.ZodOptional<z.ZodEnum<{
3595
+ public: "public";
3596
+ internal: "internal";
3597
+ confidential: "confidential";
3598
+ restricted: "restricted";
3599
+ }>>;
3600
+ contact: z.ZodOptional<z.ZodObject<{
3601
+ name: z.ZodOptional<z.ZodString>;
3602
+ email: z.ZodOptional<z.ZodString>;
3603
+ phone: z.ZodOptional<z.ZodString>;
3604
+ website: z.ZodOptional<z.ZodString>;
3605
+ }, z.core.$strict>>;
3606
+ copyright: z.ZodOptional<z.ZodString>;
3607
+ animation: z.ZodOptional<z.ZodObject<{
3608
+ transition: z.ZodOptional<z.ZodEnum<{
3609
+ push: "push";
3610
+ none: "none";
3611
+ fade: "fade";
3612
+ wipe: "wipe";
3613
+ }>>;
3614
+ elements: z.ZodOptional<z.ZodEnum<{
3615
+ none: "none";
3616
+ auto: "auto";
3617
+ }>>;
3618
+ }, z.core.$strict>>;
3619
+ }, z.core.$strict>>;
3620
+ brand: z.ZodOptional<z.ZodObject<{
3621
+ logo_asset_id: z.ZodOptional<z.ZodString>;
3622
+ position: z.ZodOptional<z.ZodEnum<{
3623
+ tl: "tl";
3624
+ tr: "tr";
3625
+ bl: "bl";
3626
+ br: "br";
3627
+ }>>;
3628
+ }, z.core.$strict>>;
3629
+ branding: z.ZodOptional<z.ZodEnum<{
3630
+ full: "full";
3631
+ "cover-only": "cover-only";
3632
+ minimal: "minimal";
3633
+ }>>;
3634
+ pages: z.ZodArray<z.ZodObject<{
3635
+ id: z.ZodString;
3636
+ type: z.ZodEnum<{
3637
+ content: "content";
3638
+ cover: "cover";
3639
+ chapter: "chapter";
3640
+ ending: "ending";
3641
+ }>;
3642
+ heading: z.ZodString;
3643
+ beat: z.ZodOptional<z.ZodEnum<{
3644
+ dense: "dense";
3645
+ anchor: "anchor";
3646
+ breathing: "breathing";
3647
+ }>>;
3648
+ focus: z.ZodOptional<z.ZodString>;
3649
+ summary: z.ZodOptional<z.ZodString>;
3650
+ }, z.core.$strict>>;
3651
+ }, z.core.$strict>;
3652
+ type DeckSpec = z.infer<typeof DeckSpecSchema>;
3653
+ /** JSON Schema for the deck spec — feed this to a model before it writes one (see `pptwise schema --spec`). */
3654
+ declare function specJsonSchema(): Record<string, unknown>;
3655
+ interface SpecValidationIssue {
3656
+ path: string;
3657
+ message: string;
3658
+ /** The offending page's `id`, when the issue is scoped to one specific
3659
+ * page and that page's `id` could be determined — absent for deck-level
3660
+ * issues (e.g. "pages non-empty") and for structural issues on a page
3661
+ * whose own `id` itself failed to parse. */
3662
+ pageId?: string;
3663
+ }
3664
+ interface SpecValidateResult {
3665
+ ok: boolean;
3666
+ spec?: DeckSpec;
3667
+ errors: SpecValidationIssue[];
3668
+ /**
3669
+ * Same shape and channel as `ValidateResult.normalized` (`../validate-core.ts`)
3670
+ * — human-readable `path: alias → canonical`-style rewrite entries for every
3671
+ * deterministic pre-parse rewrite `validateSpec` applied before parsing.
3672
+ * Sources: `normalizeDeckRootAliases` (`chrome` → `branding`) and
3673
+ * `normalizeNarrativeShape` (`../narrative`, T0b fix 2): a top-level
3674
+ * `narrative: {id: "<preset>"}` shape rewritten to the bare preset string.
3675
+ * Present only when at least one rewrite happened; informational, never
3676
+ * gates `ok` on its own.
3677
+ * `cli/commands.ts`'s `runSpecValidate` prints this through the same
3678
+ * `normalizedNote` helper `runValidate`/`runRender` already use for the
3679
+ * bare-IR path's own component field-alias notes.
3680
+ */
3681
+ normalized?: string[];
3682
+ }
3683
+ declare function formatSpecIssues(errors: SpecValidationIssue[]): string;
3684
+ /**
3685
+ * Deck-spec-level theme default (spec §5's defaults chain: "theme omitted →
3686
+ * consulting") — the same default IR's own `theme.id` field carries
3687
+ * (`ThemeSchema` in `ir/index.ts`). Exported so a caller already holding a
3688
+ * validated {@link DeckSpec} (the CLI's OK-summary line) doesn't re-derive
3689
+ * the fallback itself.
3690
+ */
3691
+ declare function resolveSpecThemeId(spec: DeckSpec): string;
3692
+ /**
3693
+ * Deck-level page-count range per pacing (spec §5's pacing table,
3694
+ * initial values — "dense 8-30 / balanced 6-24 / spacious 4-16", not yet
3695
+ * tuned against real usage). Independent of `PACING_BUDGETS`
3696
+ * (`narrative/index.ts`, per-slide component-count/bullets editorial
3697
+ * budget) — this is a separate, deck-wide page-count concern the spec calls
3698
+ * out as its own hard gate ("page count vs. pacing recommended range").
3699
+ * Message wording renamed from "delivery" to "pacing" (vocabulary-v4
3700
+ * residual, routed from the task 1 review) — the axis itself was already
3701
+ * `Pacing` at the type level, this closes the last stale word in the
3702
+ * error text.
3703
+ */
3704
+ declare const SPEC_PAGE_COUNT_RANGE: Record<Pacing, {
3705
+ min: number;
3706
+ max: number;
3707
+ }>;
3708
+ /**
3709
+ * Validate raw JSON against the spec schema, then — once it parses — run the
3710
+ * spec §5 hard-gate chain. Mirrors `validateIr`'s (`api.ts`) overall shape: a
3711
+ * structural zod pass first, then a sequence of isolated hard-gate
3712
+ * categories, each short-circuiting the whole chain on its own failure
3713
+ * (rather than accumulating errors across categories) so a later category
3714
+ * never has to guess at what an earlier, already-broken one would have
3715
+ * meant — e.g. beat-rotation and page-count both need a resolved
3716
+ * narrative, so nothing past the narrative/theme stage runs until that
3717
+ * resolves cleanly. Every spec-gate philosophy here is "hard block, no soft
3718
+ * warning" (spec §5's "escape hatch" section — a spec that doesn't fit this shape
3719
+ * should be authored as bare IR instead, not warned-and-shipped).
3720
+ *
3721
+ * Before the schema parse, {@link normalizeNarrativeShape} (`../narrative`,
3722
+ * T0b fix 2 scope extension) runs on the raw input, exactly mirroring
3723
+ * `validateIr`'s own pre-parse pass (`../validate-core.ts`) — a top-level
3724
+ * `narrative: {id: "<preset>"}` shape is rewritten to the bare preset
3725
+ * string before `DeckSpecSchema.safeParse` ever sees it, so the correction
3726
+ * lands in the returned `spec` itself (read again by `checkBeatRotation`/
3727
+ * `checkFocusVocabulary`/`checkPageCount` below, and by `runSpecValidate`'s
3728
+ * own OK-summary line, `../cli/commands.ts`), not just this function's own
3729
+ * local `resolveNarrative` call below. Every return path is wrapped in
3730
+ * `withNormalized` so the rewrite note (`SpecValidateResult.normalized`)
3731
+ * surfaces on success *or* failure, success or failure alike — same
3732
+ * "informational, never gates `ok`" contract `ValidateResult.normalized`
3733
+ * has.
3734
+ */
3735
+ declare function validateSpec(input: unknown): SpecValidateResult;
3736
+
3737
+ /**
3738
+ * One page's fillable content — everything a page spec's `id` does *not*
3739
+ * already lock in. Deliberately excludes `type`/`heading` (spec-owned, see
3740
+ * {@link assembleDeck}'s locked-field gate) and `subheading`/`decor`
3741
+ * (legitimate `Slide` fields, but outside this record's shape by spec §7's
3742
+ * own layout — "pages/<id>.json contains only components" — a spec/pages deck can't
3743
+ * author either one — a hand-authored bare IR still can). Every field here is
3744
+ * a same-name, same-shape subset of `Slide`'s own optional fields
3745
+ * (`../ir`'s `SlideSchema`) — reused, not redeclared, so the two can't drift.
3746
+ */
3747
+ interface PageContent {
3748
+ components?: Component[];
3749
+ layout?: string;
3750
+ arrangement?: NonNullable<Slide["arrangement"]>;
3751
+ background?: BackgroundSpec;
3752
+ image_side?: "left" | "right";
3753
+ footnote?: string;
3754
+ notes?: string;
3755
+ }
3756
+ interface AssembleResult {
3757
+ ir: PptxIR;
3758
+ /**
3759
+ * Set only when `spec.seed` was absent and {@link assembleDeck} generated
3760
+ * one deterministically (see the seed section of this function's doc
3761
+ * comment) — `undefined` when `spec.seed` was already present and simply
3762
+ * passed through. The CLI shell (W5 task 5) is the one that acts on this:
3763
+ * suggest writing the value back into the spec file, never rewrite it
3764
+ * itself (assemble stays a pure function, no fs side effects here).
3765
+ */
3766
+ generatedSeed?: number;
3767
+ /**
3768
+ * Count of pages whose `layout` field this call filled in via
3769
+ * {@link materializeEffectiveLayouts} (W4 design decision 10) —
3770
+ * `undefined` when zero, same "absent means nothing to report" posture as
3771
+ * {@link generatedSeed} just above (a page with an explicit pin already in
3772
+ * its page file, or every omitted page landing on the image-cover
3773
+ * takeover's `null` bypass, both leave this unset). Purely informational:
3774
+ * the CLI shell (`runAssemble`, `../cli/commands.ts`) surfaces it as a
3775
+ * one-line note, nothing here or downstream acts on the number itself.
3776
+ */
3777
+ materializedLayoutCount?: number;
3778
+ }
3779
+ /**
3780
+ * Assemble a validated deck spec plus a per-page content record into a
3781
+ * renderable IR. See this module's own top comment for the overall shape.
3782
+ * Step numbers below match the W5 task-3 brief's own numbered "inject
3783
+ * semantics" list verbatim — kept in that exact order because two of them
3784
+ * (locked-field vs. orphan) both throw and their relative order is
3785
+ * otherwise unobservable from either doc comment alone.
3786
+ *
3787
+ * 1. `spec` is `unknown` (same boundary `validateSpec` itself has — a spec
3788
+ * is almost always freshly `JSON.parse`d off disk by the caller) —
3789
+ * invalid shape or a failed hard gate throws {@link PptwiseError} with
3790
+ * `validateSpec`'s own formatted issue list, not a re-derived message.
3791
+ * 2. Shape guard + locked-field protection: a `pages[id]` entry must first be
3792
+ * a plain object — not `null`, an array, or a primitive — else throws.
3793
+ * `pages` is `unknown`-shaped off disk same as `spec` itself (step 1), so
3794
+ * a JSON `null`/string/array content value is a real possibility, not
3795
+ * just a type-system hole, and `Object.hasOwn` throws its own
3796
+ * uninformative native `TypeError` on `null` (and silently no-ops on a
3797
+ * string/array/number) rather than this gate's own readable message.
3798
+ * Once that holds, a `pages[id]` entry that carries a `type` or `heading`
3799
+ * *key* — even set to `undefined` — throws. `Object.hasOwn`, not a
3800
+ * `!== undefined` read, is what makes that "even `undefined`" case
3801
+ * catchable: `PageContent` itself never declares either field, but a
3802
+ * page file freshly parsed off disk is `unknown` before it reaches this
3803
+ * function's declared `PageContent` parameter type, so a stray
3804
+ * `"heading": null`-turned-`undefined` or a copy-pasted empty key is
3805
+ * exactly the drift this gate exists to catch instead of silently
3806
+ * ignoring.
3807
+ * 3. Orphan keys: a `pages` entry whose id isn't any spec page's id. Listed
3808
+ * together with a fix suggestion, checked only after every present page
3809
+ * has cleared the locked-field gate (step 2) — an orphan file that
3810
+ * *also* happens to redeclare `heading` reports as locked-field first.
3811
+ * 4. Missing pages (a spec id with no `pages` entry) become a placeholder
3812
+ * slide — never an error. Spec §7's own words: "assemble's precise
3813
+ * semantics — a missing page always succeeds (placeholder), a structural
3814
+ * contradiction (orphan file / bad spec / id conflict) errors". A
3815
+ * declared `summary` becomes the placeholder's `subheading` so a
3816
+ * `--draft` preview of an unfilled deck still reads as more than a bare
3817
+ * "Untitled". Step 5 preserves the same visible line for filled boundary
3818
+ * pages while keeping it fill-only on filled content pages. A declared
3819
+ * `beat` carries straight into the
3820
+ * placeholder's own `Slide.beat` too (P1 variety wave, task 1 — see step
3821
+ * 5's note below). A placeholder page still participates in this
3822
+ * function's own layout materialization below, so its beat still needs
3823
+ * to reach the IR for that weighting to see it once the page is filled
3824
+ * in later without re-materializing.
3825
+ * 5. Present pages become a full slide: `id`/`type`/`heading` from the page
3826
+ * spec (never the content record — see step 2), plus whichever of
3827
+ * {@link PageContent}'s seven fields the content record actually set.
3828
+ * `beat` now carries straight into the IR's own `Slide.beat` field too —
3829
+ * reached from `PageSpec`, not `PageContent` (same source as `id`/`type`/
3830
+ * `heading`, since `beat` is spec-owned, not per-page content) — as of
3831
+ * the P1 variety wave's task 1. Previously dropped here as a spec-only
3832
+ * authoring anchor, `SlideSchema.beat`'s own doc comment
3833
+ * (`../ir/index.ts`) has the full accounting of what it now does
3834
+ * downstream. `focus` remains a spec-only authoring anchor. A filled
3835
+ * boundary page (`cover`, `chapter`, or `ending`) carries `summary` into
3836
+ * `subheading`, because its page file has no body-content field that can
3837
+ * express the line after filling. A filled `content` page keeps summary
3838
+ * spec-only, so its fill prompt does not become duplicate visible slide
3839
+ * content.
3840
+ * 6. Top-level: `version` is always the literal `"4"` (IR's own version,
3841
+ * unrelated to the deck spec's own `version: "1"`) — the deck spec's own
3842
+ * `narrative` field (renamed this task from `scenario`, spec §8.1's
3843
+ * `DeckPlan`→`DeckSpec` rename, task 2) carries across into the v4 IR's
3844
+ * own `narrative` field, its value already in the new strategy/pacing
3845
+ * vocabulary (vocabulary-v4 rename, task 1 — `spec/index.ts`'s own
3846
+ * `resolveNarrative` call already validates it against that vocabulary
3847
+ * before this function ever runs).
3848
+ * `theme`/`filename`/`brand`/`branding`/`meta`/`seed` (step 7) carry over from the spec when
3849
+ * present. When absent, this function omits the field from the raw
3850
+ * object it hands to {@link PptxIRSchema} rather than re-deriving IR's
3851
+ * own default value a second time — one default source of truth, the
3852
+ * schema itself (e.g. `theme` omitted here becomes `{ id: "consulting" }`,
3853
+ * exactly like a bare hand-authored IR that never mentions theme at all —
3854
+ * not a value this function needs to know). `branding` is the same omit
3855
+ * posture: a spec that never sets it produces an IR that never sets it,
3856
+ * and the renderer treats that as `"cover-only"`. This function must not
3857
+ * infer branding from narrative.
3858
+ * 7. Seed: `spec.seed` present → passed through, `generatedSeed` stays
3859
+ * `undefined` on the result. Absent → {@link generateSeed} derives one
3860
+ * from `filename` + the spec's own ordered page-id list (never page
3861
+ * *content* — see that function's own doc comment for why), written to
3862
+ * `ir.seed` *and* returned as `generatedSeed` so a CLI shell can suggest
3863
+ * writing it back into the spec file (this function never touches disk
3864
+ * itself).
3865
+ * 8. Idempotence: every step above is a pure function of its inputs (no
3866
+ * randomness, no wall-clock, no reliance on unordered iteration) — two
3867
+ * calls with structurally-equal `spec`/`pages` produce deep-equal
3868
+ * results, `generatedSeed` included. Exercised directly by this module's
3869
+ * test suite rather than asserted here.
3870
+ */
3871
+ declare function assembleDeck(spec: unknown, pages: Record<string, PageContent>): AssembleResult;
3872
+ /**
3873
+ * Inverse of {@link assembleDeck}: reconstructs `{ spec, pages }` from an
3874
+ * existing IR well enough that `assembleDeck(...disassembleDeck(ir))`
3875
+ * reproduces the deck-project content surface. The map is not lossless for
3876
+ * fields that the spec/pages format cannot represent. The known losses and
3877
+ * recoveries are:
3878
+ *
3879
+ * - `focus` never appears on any produced {@link PageSpec} — it is a
3880
+ * spec-only authoring anchor with no corresponding `Slide` field at all
3881
+ * (see {@link assembleDeck} step 5's doc comment). Nothing here could
3882
+ * recover a value that was never written anywhere. `beat` used to share
3883
+ * this fate but no longer does (P1 variety wave, task 1): it is now a
3884
+ * plain passthrough on both sides, exactly like `layout`/`heading` below —
3885
+ * `assembleDeck` step 5/6 reads `pageSpec.beat` into `slide.beat`, this
3886
+ * function reads `slide.beat` straight back below.
3887
+ * - `summary` is recovered from `subheading` for every placeholder slide and
3888
+ * for filled boundary slides (`cover`, `chapter`, and `ending`). This
3889
+ * reverses steps 4 and 5 without losing their visible subtitle. A filled
3890
+ * content slide's own `subheading` remains a separate authored field. It
3891
+ * has no {@link PageContent} field to land in, and treating it as summary
3892
+ * would turn visible slide copy into a fill-only prompt, so it is dropped.
3893
+ * Same for `decor`: a real `Slide` field absent from `PageContent`.
3894
+ * - `theme.style` / `theme.brand` overrides collapse to a bare theme-id
3895
+ * string (`DeckSpecSchema.theme` has no shape for either) — only `theme.id`
3896
+ * survives. That `theme.brand` is `ThemeSchema.brand` (`BrandConfigSchema`
3897
+ * — `suppressFooterOnCardContent`/`suppressFooterRule`/`suppressFooterMeta`,
3898
+ * brand-footer flags owned by the *theme*) — not to be confused with the deck-level
3899
+ * `brand` field below, a different, unrelated schema despite the shared
3900
+ * name.
3901
+ * - `ir.assets.images` is not part of this function's return value at all —
3902
+ * `{ spec, pages }` has no `assets` field, and this module stays zero-fs
3903
+ * by design (this file's own top comment), so it has no way to write an
3904
+ * `assets/` directory itself. Any `asset_id` reference inside a copied
3905
+ * `components`/`background` survives untouched ({@link extractPageContent}
3906
+ * copies both as-is), but the underlying image bytes are deliberately left
3907
+ * for the caller: the CLI shell closes that gap. `runDisassemble`
3908
+ * (`../cli/commands.ts`) walks the *input* IR's `ir.assets.images` itself
3909
+ * and materializes every entry into `<outDir>/assets/<id>.<ext>`
3910
+ * (`writeDeckAssets`, `../cli/deck-dir.ts`) so a later `readDeckDir`'s
3911
+ * `scanAssets` re-registers them exactly like any other deck-directory
3912
+ * asset. Skipping that shell-side step is not a doc-comment nuance — it
3913
+ * reproduces as a real bug: an image deck disassembles with every
3914
+ * `asset_id` left dangling, then re-assembles and renders with the image
3915
+ * silently missing.
3916
+ *
3917
+ * Round-trip-safe despite the above, worth calling out because of that name
3918
+ * collision: the top-level `brand` field (`BrandSchema` — `logo_asset_id` /
3919
+ * `position`, the deck logo/position `Branding` reads,
3920
+ * `src/svg/branding.tsx`) is a plain passthrough on both sides
3921
+ * ({@link assembleDeck} step 6 reads `spec.brand` into `ir.brand` — this
3922
+ * function reads `ir.brand` back into `spec.brand` below) — carried through
3923
+ * unmodified, same as `narrative`/`filename`/`seed`/`branding`, never
3924
+ * synthesized or dropped.
3925
+ *
3926
+ * `layout` deserves a different kind of callout: it round-trips as plain
3927
+ * content like any other field ({@link extractPageContent} copies
3928
+ * `slide.layout` into `PageContent.layout` whenever the slide has one, no
3929
+ * special case) — but a `deck.json` produced by {@link assembleDeck} now
3930
+ * carries a `layout` on nearly every slide (W4 design decision 10's
3931
+ * materialization, see that function's own doc comment), even on pages whose
3932
+ * *original page file* never set one. Nothing on `Slide` marks which is
3933
+ * which — `layout` is just a string either way — so disassembling an
3934
+ * already-assembled `deck.json` writes every materialized pick into the
3935
+ * regenerated page file as if it had been an explicit pin all along, and a
3936
+ * later re-`assembleDeck` call skips materialization for that page from then
3937
+ * on, same as any hand-authored pin. Accepted, not a bug: `disassembleDeck`
3938
+ * is a one-time bare-IR importer into an editable project directory, not a
3939
+ * round-trip channel for `deck.json` itself. A deck project's own
3940
+ * `pages/<id>.json` files are the durable, edit-in-place artifacts —
3941
+ * `deck.json` is downstream output, and feeding it back through
3942
+ * `disassembleDeck` (instead of editing the project directory that produced
3943
+ * it) is what actually costs those pages their revision-stability
3944
+ * re-selection eligibility going forward. A real, user-visible narrowing,
3945
+ * worth knowing about here, not worth adding code to guard against for a
3946
+ * usage pattern nothing in this codebase actually exercises.
3947
+ *
3948
+ * Two structural fields are synthesized rather than copied when the source
3949
+ * slide omits them, each documented where it is generated:
3950
+ * {@link UNTITLED_HEADING} for a missing/blank `heading`, and a positional
3951
+ * `p-<1-based-ordinal>-<type>` scheme for a missing `id` (stable across
3952
+ * repeated calls on the same IR — it is a pure function of slide position
3953
+ * and type — but, unlike a spec-assigned id, *not* stable across inserting
3954
+ * or reordering slides — out of scope here, since a bare IR with no `id` at
3955
+ * all has no stabler identity to fall back to in the first place).
3956
+ *
3957
+ * That generated `p-<ordinal>-<type>` id is safe by construction as a
3958
+ * page/asset file-name segment (W5 whole-branch review finding 1, verified
3959
+ * — not just asserted — rather than also routing it through
3960
+ * `assertSafeFileSegment`, `../cli/deck-dir.ts`): `<ordinal>` is
3961
+ * `index + 1`, always a plain non-negative integer, and `<type>` is a
3962
+ * `Slide["type"]`, a closed schema enum (`"cover" | "chapter" | "content" |
3963
+ * "ending"`, `SlideSchema.type` in `../ir/index.ts`) — neither half can ever
3964
+ * contain a `/`, a `\`, or resolve to `".."`, so the joined id can't either.
3965
+ * A carried-over `slide.id` (the `??` branch's other side) has no such
3966
+ * guarantee — that is the one this function passes straight through
3967
+ * unchecked, same as every other field {@link extractPageContent} copies —
3968
+ * which is exactly why the write-time gate in `../cli/deck-dir.ts` (not this
3969
+ * function) is what actually closes the vulnerability.
3970
+ */
3971
+ declare function disassembleDeck(ir: PptxIR): {
3972
+ spec: DeckSpec;
3973
+ pages: Record<string, PageContent>;
3974
+ };
3975
+
3976
+ declare function migrateDeckPlanToSpec(raw: unknown): unknown;
3977
+
3978
+ /**
3979
+ * `pptwise audit` finding shape (v0.3 W6, spec §7 workflow ④). `page` is
3980
+ * 1-based (matches `ValidationIssue.page` in `api.ts`); `slideId` is set
3981
+ * whenever the offending slide has a stable `id` (assemble-stamped or
3982
+ * author-supplied), same "page + optional id" convention `formatIssues`
3983
+ * already uses for validate errors.
3984
+ */
3985
+ interface AuditFinding {
3986
+ page: number;
3987
+ slideId?: string;
3988
+ code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap" | "content-truncated" | "content-dropped";
3989
+ message: string;
3990
+ detail?: Record<string, unknown>;
3991
+ }
3992
+ /**
3993
+ * Which check families actually ran (audit-v2 phase B, spec §4.2/§11.6) —
3994
+ * the one piece of the spec's original metrics design that survives into
3995
+ * this wave (§11.6: metrics itself deferred, "唯一现值是 checks 结构"). This
3996
+ * is the wave's own spirit made literal: a field can only ever be
3997
+ * `"completed"` once its check family genuinely ran over every audited
3998
+ * page, never a default that silently reads as "passed". `svg` has no
3999
+ * `"not-requested"` state — the deterministic SVG audit always runs, so it
4000
+ * is always `"completed"`. `pixels` starts `"not-requested"` and only ever
4001
+ * becomes `"completed"`; there is deliberately no `"failed"` value — a
4002
+ * failed pixel audit throws (spec §11.7's "契约层"), it never reports
4003
+ * itself as a completed check that happens to carry no findings.
4004
+ */
4005
+ interface AuditChecks {
4006
+ svg: "completed";
4007
+ pixels: "not-requested" | "completed";
4008
+ }
4009
+ interface AuditReport {
4010
+ findings: AuditFinding[];
4011
+ pagesAudited: number;
4012
+ pagesSkipped: number;
4013
+ checks: AuditChecks;
4014
+ }
4015
+ /** `auditDeck`'s second parameter (audit-v2 phase B) — see that function's
4016
+ * own doc comment for the overload contract this shape backs. */
4017
+ interface AuditDeckOptions {
4018
+ pixels?: boolean;
4019
+ }
4020
+ /**
4021
+ * The SDK entry point. Advisory, not a hard gate: `validateIr` already
4022
+ * rejects structurally invalid or over-dense decks before a caller ever
4023
+ * gets this far; this function looks for the visual problems that can still
4024
+ * slip through a valid deck at render time (an author-chosen near-background
4025
+ * text color, two components whose combined content happens to collide, a
4026
+ * card list that had to drop an item to fit). A non-empty `findings` array
4027
+ * is a prompt for a human/agent to look, not a rejection.
4028
+ *
4029
+ * Placeholder pages (`slide.placeholder === true`) are skipped — assemble's
4030
+ * stand-in for content nobody has written yet has nothing to audit, same
4031
+ * reasoning `checkIrQuality` already uses to skip them (`ir-quality.ts`).
4032
+ *
4033
+ * `auditDeck` itself never calls `installNodePlatform()`; that's the
4034
+ * caller's job (the CLI does it automatically).
4035
+ *
4036
+ * `opts.pixels` (audit-v2 phase B, spec §4.3/§11.7) opts into the optional
4037
+ * pixel-level contrast audit over image-backed text (`../svg/audit/pixel-audit.ts`)
4038
+ * — overloaded so the far more common omitted/`false` case keeps returning
4039
+ * a plain, synchronous `AuditReport` (spec §11.7's "语义层": the default
4040
+ * audit stays pure TS with zero added latency or Promise-wrapping), while
4041
+ * `pixels: true` returns a `Promise<AuditReport>` that resolves once the
4042
+ * rasterization pass completes. A caller holding a non-literal
4043
+ * `{ pixels: someBoolean }` won't match either overload directly (TypeScript
4044
+ * can't narrow it to one specific branch) — branch on the boolean first and
4045
+ * call this function with a literal in each arm, e.g. `opts.pixels ? await
4046
+ * auditDeck(ir, { pixels: true }) : auditDeck(ir)` (`cli/commands.ts`'s
4047
+ * `runAudit` does exactly this).
4048
+ */
4049
+ declare function auditDeck(ir: PptxIR, opts?: {
4050
+ pixels?: false;
4051
+ }): AuditReport;
4052
+ declare function auditDeck(ir: PptxIR, opts: {
4053
+ pixels: true;
4054
+ }): Promise<AuditReport>;
4055
+
4056
+ /**
4057
+ * Real rendered frame for one image slot, in canvas px (0-1280 × 0-720 —
4058
+ * `../constants`'s `CANVAS_W_PX`/`CANVAS_H_PX`). This is the whole point of
4059
+ * this module (asset-brief plan, 裁定 1): it comes from parsing an actual
4060
+ * render pass's SVG output, never from hand-copying `image.tsx`'s
4061
+ * `w * 0.5`/`MAX_IMAGE_H` constants — those change independently of this
4062
+ * file, and a shadow copy would silently start lying the day they do.
4063
+ */
4064
+ interface AssetBriefFrame {
4065
+ x: number;
4066
+ y: number;
4067
+ w: number;
4068
+ h: number;
4069
+ /** e.g. "2:1" when w:h reduces to a small clean ratio, else "1.63:1". */
4070
+ aspect: string;
4071
+ }
4072
+ interface AssetBriefFit {
4073
+ mode: "cover" | "contain";
4074
+ /** One sentence: crop behavior + safe-zone guidance for a generator. */
4075
+ note: string;
4076
+ }
4077
+ interface AssetBriefPalette {
4078
+ /** Deduplicated theme hex values, in `StyleColors`' own field order. */
4079
+ hexes: string[];
4080
+ primary: string;
4081
+ accent: string;
4082
+ }
4083
+ interface AssetBriefMood {
4084
+ /** `ThemeDefinition.tags` passed through as-is (empty for every one of
4085
+ * the 13 builtins today — none has adopted this field yet — but a
4086
+ * registered custom theme may set it). */
4087
+ tags: readonly string[];
4088
+ /** One sentence assembled from the theme's own label/motif — never a
4089
+ * hand-written per-theme paragraph (asset-brief plan 裁定: "不新写 13 段
4090
+ * 文案"). */
4091
+ description: string;
4092
+ }
4093
+ interface AssetBriefPage {
4094
+ index: number;
4095
+ id?: string;
4096
+ type: Slide["type"];
4097
+ heading?: string;
4098
+ }
4099
+ interface AssetBriefItem {
4100
+ page: AssetBriefPage;
4101
+ asset_id: string;
4102
+ /** `ir.assets.images[asset_id].alt` passed straight through (A11Y-01 alt
4103
+ * chain wave, task 1) — this is already a per-asset structure, so a
4104
+ * generator agent filling in `missing`/`suggested_prompt` items sees
4105
+ * right alongside them which ids already have an accessibility
4106
+ * description written and which don't. Omitted (not an empty string)
4107
+ * when the asset has no `alt` — same "don't fabricate a value that
4108
+ * isn't there" discipline the rest of this file already follows for
4109
+ * `frame`/`suggested_pixels`. */
4110
+ alt?: string;
4111
+ /** Discriminant reserved for v2 (asset-brief plan 裁定 4): only `"image"`
4112
+ * is produced today (one `AssetBriefItem` per `image` component, or per
4113
+ * shared frame — see `shared` below). `"background"` is the documented
4114
+ * extension slot for background asset specs, deliberately not built in
4115
+ * v1 (no `background` geometry extraction exists yet) — this field lets
4116
+ * a future v2 add it without a breaking shape change for consumers that
4117
+ * already switch on `kind`. */
4118
+ kind: "image";
4119
+ /** No usable `src` in `ir.assets.images` for this `asset_id` — this is a
4120
+ * generation to-do item, not a defect (asset-brief plan 裁定 2). */
4121
+ missing: boolean;
4122
+ /** `false` when the selected layout dropped this image component
4123
+ * entirely (e.g. an overflow guard) — still listed rather than silently
4124
+ * dropped (task brief's explicit requirement). `frame`/`suggested_pixels`
4125
+ * are omitted in that case; there is no real geometry to report. */
4126
+ rendered: boolean;
4127
+ frame?: AssetBriefFrame;
4128
+ /** 2× the frame's own pixel dimensions — omitted alongside `frame` when `rendered` is false. */
4129
+ suggested_pixels?: {
4130
+ w: number;
4131
+ h: number;
4132
+ };
4133
+ fit: AssetBriefFit;
4134
+ palette: AssetBriefPalette;
4135
+ mood: AssetBriefMood;
4136
+ /** One English paragraph, paste-ready for an image-generation tool. */
4137
+ suggested_prompt: string;
4138
+ /** True when this item's `frame` cannot be attributed to one specific
4139
+ * `image` component: `occurrenceCount` (>=2) `image` components on this
4140
+ * same page share this `asset_id`. Because a shared `asset_id` resolves
4141
+ * to the exact same dummy href in the render-only pass, every occurrence
4142
+ * produces an *identical* `<image>` `href` in the rendered SVG — there is
4143
+ * no signal left to tell which `<image>` element came from which
4144
+ * component, in principle, not just in this implementation. Rather than
4145
+ * guess (see `buildAssetBrief`'s own doc comment for the bug this
4146
+ * replaced), every real rendered frame for the shared `asset_id` gets its
4147
+ * own item, all flagged `shared: true`, none claiming a specific
4148
+ * component. Omitted (not `false`) for the single-occurrence case — the
4149
+ * overwhelmingly common one — so that path's output shape is byte-for-
4150
+ * byte what it was before this field existed. */
4151
+ shared?: true;
4152
+ /** Present iff `shared` is true: how many `image` components on this page
4153
+ * reference this same `asset_id`. */
4154
+ occurrenceCount?: number;
4155
+ }
4156
+ interface AssetBrief {
4157
+ theme: string;
4158
+ items: AssetBriefItem[];
4159
+ }
4160
+ /**
4161
+ * Build an image-generation brief for every `image` component in a deck: the
4162
+ * real rendered frame (from an actual render pass — see `extractImageFrames`),
4163
+ * fit/crop mode with a safe-zone note, suggested generation pixels (2× the
4164
+ * frame), the resolved theme's palette/mood, and a paste-ready English
4165
+ * `suggested_prompt`. Pure function of `ir` (same input → same output, proven
4166
+ * by `asset-brief.test.ts`'s double-call check) — like `auditDeck`, it never
4167
+ * mutates its argument and never touches the filesystem or network.
4168
+ *
4169
+ * v1 scope (asset-brief plan 裁定 4): only `image`-typed components — not
4170
+ * `image_grid`/`image_compare` (separate component types with their own
4171
+ * asset_id arrays), and not `background` asset specs. Both are natural v2
4172
+ * extensions of this same shape and are deliberately left off `AssetBrief`
4173
+ * rather than bolted on half-done.
4174
+ *
4175
+ * Missing-asset handling (裁定 2): every `image` component's `asset_id`,
4176
+ * present or not, gets overridden to the same dummy 1×1 PNG (tagged with a
4177
+ * `#<asset_id>` fragment) in an in-memory copy of `ir` used *only* for this
4178
+ * function's own extraction render pass — `ir` itself, and the real render/
4179
+ * export path, never see this override. `missing` below is decided from the
4180
+ * real `ir.assets.images` (before the override), so it still accurately
4181
+ * reports "nothing usable was ever supplied here" even though the
4182
+ * extraction render always has *something* to draw. Overriding every
4183
+ * asset_id uniformly (not just the missing ones) is what makes the fragment
4184
+ * a reliable identifier regardless of whether the real asset was already
4185
+ * resolved — the alternative (leaving resolved assets' real `src` alone)
4186
+ * would have no way to tell two different real images' hrefs apart when a
4187
+ * page has more than one.
4188
+ *
4189
+ * Known limitation: an `asset_id` shared between an `image` component and a
4190
+ * `background` asset spec on the same page would get the same dummy
4191
+ * fragment on both, which could ambiguate which rendered `<image>` this
4192
+ * function attributes to the component — rare in practice (v1 doesn't cover
4193
+ * background geometry at all, see above) and left as a documented gap
4194
+ * rather than engineered around.
4195
+ *
4196
+ * Shared-`asset_id` handling (task reviewer finding, fixed after v1's first
4197
+ * pass): the IR schema places no uniqueness constraint on `asset_id`
4198
+ * (`src/ir/index.ts`) — two different `image` components on the same page
4199
+ * legally reference the same one. Because the override above (deliberately)
4200
+ * maps a shared `asset_id` to one shared dummy href, both occurrences render
4201
+ * an *identical* `<image href>` — the rendered SVG carries no signal left to
4202
+ * tell them apart. (v1's first pass tried anyway: a FIFO queue keyed by
4203
+ * `asset_id`, drained in `slide.components` order under the assumption that
4204
+ * extraction order would match — it doesn't in general, e.g.
4205
+ * `content-image-lead-split.tsx` renders its narrow-column body *before*
4206
+ * its visual-lead column in the JSX it returns, so the queue was backwards
4207
+ * and every frame ended up attributed to the wrong occurrence. Not a fixable
4208
+ * ordering bug: no ordering convention could be relied on across every
4209
+ * layout's own JSX emission order, present and future.) The honest fix:
4210
+ * per page, group `image` components by `asset_id` first. A group of size 1
4211
+ * (the overwhelmingly common case) keeps the exact single-item shape this
4212
+ * function always produced. A group of size >1 abandons per-component
4213
+ * attribution entirely — it emits one item per *real rendered frame* for
4214
+ * that `asset_id` (not per component), each flagged `shared: true` with the
4215
+ * group's `occurrenceCount`, so every real frame is still reported (nothing
4216
+ * silently dropped) without asserting a specific component<->frame pairing
4217
+ * that cannot be known. If fewer frames render than there are occurrences
4218
+ * (e.g. the layout dropped one), the shortfall is padded with `rendered:
4219
+ * false` shared items so the count of components sharing the id is still
4220
+ * fully visible to a reader of the brief.
4221
+ */
4222
+ declare function buildAssetBrief(ir: PptxIR): AssetBrief;
4223
+
4224
+ export { AUDIENCE_VALUES, type AssembleResult, type AssetBrief, type AssetBriefFit, type AssetBriefFrame, type AssetBriefItem, type AssetBriefMood, type AssetBriefPage, type AssetBriefPalette, type Assets, type Audience, type AuditChecks, type AuditDeckOptions, type AuditFinding, type AuditReport, BUILTIN_THEME_IDS, type BackgroundSpec, type BrandConfig, BrandConfigSchema, type BrandThemeFile, BrandThemeFileSchema, COMPONENT_TYPES, type Component, DEFAULT_NARRATIVE, type DeckSpec, DeckSpecSchema, type ExtractBrandThemeOptions, type Meta, NARRATIVE_PRESETS, type NarrativePreset, type NarrativeProfile, PACING_BUDGETS, PACING_VALUES, type Pacing, type PacingBudget, type PageBeat, type PageContent, type PageSpec, PageSpecSchema, type PageSpecType, PptwiseError, type PptxIR, PptxIRSchema, type PptxIRV3, PptxIRV3Schema, SPEC_PAGE_COUNT_RANGE, STRATEGY_DEFINITIONS, STRATEGY_VALUES, type Slide, type SpecValidateResult, type SpecValidationIssue, type Strategy, type StrategyDefinition, type StyleOverride, StyleOverrideSchema, type ThemeDefinition, type ThemeInfo, type ThemeRegistration, ThemeSchema, VERSION, type ValidateResult, type ValidationIssue, assembleDeck, auditDeck, buildAssetBrief, disassembleDeck, extractBrandTheme, formatIssues, formatSpecIssues, generatePptx, getInstalledThemeIds, getThemeDefinition, irJsonSchema, listThemes, migrateDeckPlanToSpec, migrateIrV3ToV4, parseBrandThemeFile, registerBrandThemeFile, registerTheme, renderSlideSvg, resolveNarrative, resolveSpecThemeId, specJsonSchema, styleJsonSchema, validateIr, validateSpec };