@liustack/pptfast 0.1.0 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { z } from 'zod';
2
- export { P as PptfastPlatform, i as installPlatform } from './registry-CYKxZ0-U.js';
2
+ export { P as PptfastPlatform, R as RasterizedImage, i as installPlatform } from './registry-V079Jkry.js';
3
3
 
4
- declare const VERSION = "0.1.0";
4
+ declare const VERSION = "0.4.0";
5
5
 
6
6
  /** Error class thrown by pptfast's public API surface (validation, rendering, export). */
7
7
  declare class PptfastError extends Error {
8
8
  }
9
9
 
10
- declare const THEME_IDS: readonly ["consulting", "enterprise", "academic", "insight", "campaign", "bloom", "classroom", "ink", "tech", "runway", "journal", "luxe", "heritage"];
10
+ declare const BUILTIN_THEME_IDS: readonly ["consulting", "enterprise", "academic", "insight", "campaign", "bloom", "classroom", "ink", "tech", "runway", "journal", "luxe", "heritage"];
11
11
  declare const BackgroundSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
12
  kind: z.ZodLiteral<"color">;
13
13
  value: z.ZodString;
@@ -32,6 +32,79 @@ declare const BackgroundSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
32
32
  contain: "contain";
33
33
  }>>;
34
34
  }, z.core.$strict>], "kind">;
35
+ /**
36
+ * Style-token override (theme.style): deep-partial palette/fonts/shape
37
+ * merged over the built-in theme (see themes/index.ts resolveStyle). Scope is
38
+ * deliberately palette-level (spec §11): no defaultBackgrounds or manifest
39
+ * overrides. gapScale range mirrors the documented sane range in
40
+ * themes/tokens.ts StyleShape.
41
+ */
42
+ declare const StyleOverrideSchema: z.ZodObject<{
43
+ colors: z.ZodOptional<z.ZodObject<{
44
+ bg: z.ZodOptional<z.ZodString>;
45
+ surface: z.ZodOptional<z.ZodString>;
46
+ panel: z.ZodOptional<z.ZodString>;
47
+ primary: z.ZodOptional<z.ZodString>;
48
+ accent: z.ZodOptional<z.ZodString>;
49
+ text: z.ZodOptional<z.ZodString>;
50
+ muted: z.ZodOptional<z.ZodString>;
51
+ border: z.ZodOptional<z.ZodString>;
52
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
53
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
54
+ cardStroke: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strict>>;
56
+ fonts: z.ZodOptional<z.ZodObject<{
57
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
60
+ }, z.core.$strict>>;
61
+ shape: z.ZodOptional<z.ZodObject<{
62
+ radius: z.ZodOptional<z.ZodNumber>;
63
+ gapScale: z.ZodOptional<z.ZodNumber>;
64
+ }, z.core.$strict>>;
65
+ }, z.core.$strict>;
66
+ type StyleOverride = z.infer<typeof StyleOverrideSchema>;
67
+ /**
68
+ * Brand (logical slide-master) config: brand-chrome behavior owned by a theme.
69
+ * W1 scope = exactly the two flags migrated from the old manifest.chrome.
70
+ * Single source of truth — the TS type is inferred, never hand-written.
71
+ */
72
+ declare const BrandConfigSchema: z.ZodObject<{
73
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
74
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
75
+ }, z.core.$strict>;
76
+ type BrandConfig = z.infer<typeof BrandConfigSchema>;
77
+ declare const ThemeSchema: z.ZodObject<{
78
+ id: z.ZodDefault<z.ZodString>;
79
+ style: z.ZodOptional<z.ZodObject<{
80
+ colors: z.ZodOptional<z.ZodObject<{
81
+ bg: z.ZodOptional<z.ZodString>;
82
+ surface: z.ZodOptional<z.ZodString>;
83
+ panel: z.ZodOptional<z.ZodString>;
84
+ primary: z.ZodOptional<z.ZodString>;
85
+ accent: z.ZodOptional<z.ZodString>;
86
+ text: z.ZodOptional<z.ZodString>;
87
+ muted: z.ZodOptional<z.ZodString>;
88
+ border: z.ZodOptional<z.ZodString>;
89
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
90
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
91
+ cardStroke: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strict>>;
93
+ fonts: z.ZodOptional<z.ZodObject<{
94
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
95
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
96
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
97
+ }, z.core.$strict>>;
98
+ shape: z.ZodOptional<z.ZodObject<{
99
+ radius: z.ZodOptional<z.ZodNumber>;
100
+ gapScale: z.ZodOptional<z.ZodNumber>;
101
+ }, z.core.$strict>>;
102
+ }, z.core.$strict>>;
103
+ brand: z.ZodOptional<z.ZodObject<{
104
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
105
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
106
+ }, z.core.$strict>>;
107
+ }, z.core.$strict>;
35
108
  declare const MetaSchema: z.ZodObject<{
36
109
  organization: z.ZodOptional<z.ZodString>;
37
110
  authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -74,7 +147,7 @@ declare const AssetsSchema: z.ZodObject<{
74
147
  error: z.ZodOptional<z.ZodString>;
75
148
  }, z.core.$strict>>>;
76
149
  }, z.core.$strict>;
77
- declare const BlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
150
+ declare const ComponentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
78
151
  type: z.ZodLiteral<"bullets">;
79
152
  items: z.ZodArray<z.ZodString>;
80
153
  style: z.ZodOptional<z.ZodEnum<{
@@ -127,9 +200,9 @@ declare const BlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
127
200
  chart_type: z.ZodEnum<{
128
201
  line: "line";
129
202
  dumbbell: "dumbbell";
203
+ funnel: "funnel";
130
204
  bar: "bar";
131
205
  pie: "pie";
132
- funnel: "funnel";
133
206
  }>;
134
207
  direction: z.ZodOptional<z.ZodEnum<{
135
208
  horizontal: "horizontal";
@@ -319,15 +392,70 @@ declare const BlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
319
392
  vs: "vs";
320
393
  before_after: "before_after";
321
394
  }>>;
395
+ }, z.core.$strict>, z.ZodObject<{
396
+ type: z.ZodLiteral<"swot">;
397
+ strengths: z.ZodArray<z.ZodString>;
398
+ weaknesses: z.ZodArray<z.ZodString>;
399
+ opportunities: z.ZodArray<z.ZodString>;
400
+ threats: z.ZodArray<z.ZodString>;
401
+ labels: z.ZodOptional<z.ZodObject<{
402
+ strengths: z.ZodOptional<z.ZodString>;
403
+ weaknesses: z.ZodOptional<z.ZodString>;
404
+ opportunities: z.ZodOptional<z.ZodString>;
405
+ threats: z.ZodOptional<z.ZodString>;
406
+ }, z.core.$strict>>;
407
+ }, z.core.$strict>, z.ZodObject<{
408
+ type: z.ZodLiteral<"bmc">;
409
+ key_partners: z.ZodArray<z.ZodString>;
410
+ key_activities: z.ZodArray<z.ZodString>;
411
+ key_resources: z.ZodArray<z.ZodString>;
412
+ value_propositions: z.ZodArray<z.ZodString>;
413
+ customer_relationships: z.ZodArray<z.ZodString>;
414
+ channels: z.ZodArray<z.ZodString>;
415
+ customer_segments: z.ZodArray<z.ZodString>;
416
+ cost_structure: z.ZodArray<z.ZodString>;
417
+ revenue_streams: z.ZodArray<z.ZodString>;
418
+ }, z.core.$strict>, z.ZodObject<{
419
+ type: z.ZodLiteral<"waterfall">;
420
+ items: z.ZodArray<z.ZodObject<{
421
+ label: z.ZodString;
422
+ value: z.ZodNumber;
423
+ kind: z.ZodOptional<z.ZodEnum<{
424
+ delta: "delta";
425
+ total: "total";
426
+ }>>;
427
+ }, z.core.$strict>>;
428
+ unit: z.ZodOptional<z.ZodString>;
429
+ }, z.core.$strict>, z.ZodObject<{
430
+ type: z.ZodLiteral<"gantt">;
431
+ items: z.ZodArray<z.ZodObject<{
432
+ label: z.ZodString;
433
+ start: z.ZodNumber;
434
+ end: z.ZodNumber;
435
+ }, z.core.$strict>>;
436
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
322
437
  }, z.core.$strict>], "type">;
438
+ /**
439
+ * All 28 component `type` discriminant values, derived from `ComponentSchema`
440
+ * itself (never hand-copied) so this list can't drift from the union above.
441
+ * Typed as plain `readonly string[]` rather than `Component["type"][]` —
442
+ * every consumer of this list (W5's plan `focus` vocabulary gate,
443
+ * `src/plan/index.ts`) tests membership of an arbitrary author-supplied
444
+ * string, and TS's `Array.includes` is invariant in its element type, so a
445
+ * narrower literal-union type would reject that call at the caller.
446
+ */
447
+ declare const COMPONENT_TYPES: readonly string[];
323
448
  declare const SlideSchema: z.ZodObject<{
324
- type: z.ZodEnum<{
449
+ type: z.ZodDefault<z.ZodEnum<{
325
450
  cover: "cover";
326
451
  chapter: "chapter";
327
452
  content: "content";
328
453
  ending: "ending";
329
- }>;
330
- variant: z.ZodOptional<z.ZodEnum<{
454
+ }>>;
455
+ id: z.ZodOptional<z.ZodString>;
456
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
457
+ layout: z.ZodOptional<z.ZodString>;
458
+ arrangement: z.ZodOptional<z.ZodEnum<{
331
459
  code: "code";
332
460
  quote: "quote";
333
461
  single: "single";
@@ -336,15 +464,11 @@ declare const SlideSchema: z.ZodObject<{
336
464
  image_focus: "image_focus";
337
465
  big_number: "big_number";
338
466
  assertion_evidence: "assertion_evidence";
339
- image_split: "image_split";
340
- image_top: "image_top";
341
- image_bottom: "image_bottom";
342
- image_annotate: "image_annotate";
343
467
  aside: "aside";
344
468
  }>>;
345
469
  heading: z.ZodOptional<z.ZodString>;
346
470
  subheading: z.ZodOptional<z.ZodString>;
347
- blocks: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
471
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
348
472
  type: z.ZodLiteral<"bullets">;
349
473
  items: z.ZodArray<z.ZodString>;
350
474
  style: z.ZodOptional<z.ZodEnum<{
@@ -397,9 +521,9 @@ declare const SlideSchema: z.ZodObject<{
397
521
  chart_type: z.ZodEnum<{
398
522
  line: "line";
399
523
  dumbbell: "dumbbell";
524
+ funnel: "funnel";
400
525
  bar: "bar";
401
526
  pie: "pie";
402
- funnel: "funnel";
403
527
  }>;
404
528
  direction: z.ZodOptional<z.ZodEnum<{
405
529
  horizontal: "horizontal";
@@ -589,6 +713,48 @@ declare const SlideSchema: z.ZodObject<{
589
713
  vs: "vs";
590
714
  before_after: "before_after";
591
715
  }>>;
716
+ }, z.core.$strict>, z.ZodObject<{
717
+ type: z.ZodLiteral<"swot">;
718
+ strengths: z.ZodArray<z.ZodString>;
719
+ weaknesses: z.ZodArray<z.ZodString>;
720
+ opportunities: z.ZodArray<z.ZodString>;
721
+ threats: z.ZodArray<z.ZodString>;
722
+ labels: z.ZodOptional<z.ZodObject<{
723
+ strengths: z.ZodOptional<z.ZodString>;
724
+ weaknesses: z.ZodOptional<z.ZodString>;
725
+ opportunities: z.ZodOptional<z.ZodString>;
726
+ threats: z.ZodOptional<z.ZodString>;
727
+ }, z.core.$strict>>;
728
+ }, z.core.$strict>, z.ZodObject<{
729
+ type: z.ZodLiteral<"bmc">;
730
+ key_partners: z.ZodArray<z.ZodString>;
731
+ key_activities: z.ZodArray<z.ZodString>;
732
+ key_resources: z.ZodArray<z.ZodString>;
733
+ value_propositions: z.ZodArray<z.ZodString>;
734
+ customer_relationships: z.ZodArray<z.ZodString>;
735
+ channels: z.ZodArray<z.ZodString>;
736
+ customer_segments: z.ZodArray<z.ZodString>;
737
+ cost_structure: z.ZodArray<z.ZodString>;
738
+ revenue_streams: z.ZodArray<z.ZodString>;
739
+ }, z.core.$strict>, z.ZodObject<{
740
+ type: z.ZodLiteral<"waterfall">;
741
+ items: z.ZodArray<z.ZodObject<{
742
+ label: z.ZodString;
743
+ value: z.ZodNumber;
744
+ kind: z.ZodOptional<z.ZodEnum<{
745
+ delta: "delta";
746
+ total: "total";
747
+ }>>;
748
+ }, z.core.$strict>>;
749
+ unit: z.ZodOptional<z.ZodString>;
750
+ }, z.core.$strict>, z.ZodObject<{
751
+ type: z.ZodLiteral<"gantt">;
752
+ items: z.ZodArray<z.ZodObject<{
753
+ label: z.ZodString;
754
+ start: z.ZodNumber;
755
+ end: z.ZodNumber;
756
+ }, z.core.$strict>>;
757
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
592
758
  }, z.core.$strict>], "type">>>;
593
759
  background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
594
760
  kind: z.ZodLiteral<"color">;
@@ -633,33 +799,43 @@ declare const SlideSchema: z.ZodObject<{
633
799
  right: "right";
634
800
  }>>;
635
801
  footnote: z.ZodOptional<z.ZodString>;
802
+ notes: z.ZodOptional<z.ZodString>;
636
803
  }, z.core.$strict>;
637
804
  declare const PptxIRSchema: z.ZodObject<{
638
- version: z.ZodLiteral<"2">;
639
- filename: z.ZodString;
640
- theme: z.ZodObject<{
641
- id: z.ZodEnum<{
642
- consulting: "consulting";
643
- enterprise: "enterprise";
644
- academic: "academic";
645
- insight: "insight";
646
- campaign: "campaign";
647
- bloom: "bloom";
648
- classroom: "classroom";
649
- ink: "ink";
650
- tech: "tech";
651
- runway: "runway";
652
- journal: "journal";
653
- luxe: "luxe";
654
- heritage: "heritage";
655
- }>;
656
- override: z.ZodOptional<z.ZodObject<{
657
- primary: z.ZodOptional<z.ZodString>;
658
- accent: z.ZodOptional<z.ZodString>;
659
- font_heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
660
- font_body: z.ZodOptional<z.ZodArray<z.ZodString>>;
805
+ version: z.ZodDefault<z.ZodLiteral<"4">>;
806
+ filename: z.ZodDefault<z.ZodString>;
807
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
808
+ theme: z.ZodDefault<z.ZodObject<{
809
+ id: z.ZodDefault<z.ZodString>;
810
+ style: z.ZodOptional<z.ZodObject<{
811
+ colors: z.ZodOptional<z.ZodObject<{
812
+ bg: z.ZodOptional<z.ZodString>;
813
+ surface: z.ZodOptional<z.ZodString>;
814
+ panel: z.ZodOptional<z.ZodString>;
815
+ primary: z.ZodOptional<z.ZodString>;
816
+ accent: z.ZodOptional<z.ZodString>;
817
+ text: z.ZodOptional<z.ZodString>;
818
+ muted: z.ZodOptional<z.ZodString>;
819
+ border: z.ZodOptional<z.ZodString>;
820
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
821
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
822
+ cardStroke: z.ZodOptional<z.ZodString>;
823
+ }, z.core.$strict>>;
824
+ fonts: z.ZodOptional<z.ZodObject<{
825
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
826
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
827
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
828
+ }, z.core.$strict>>;
829
+ shape: z.ZodOptional<z.ZodObject<{
830
+ radius: z.ZodOptional<z.ZodNumber>;
831
+ gapScale: z.ZodOptional<z.ZodNumber>;
832
+ }, z.core.$strict>>;
661
833
  }, z.core.$strict>>;
662
- }, z.core.$strict>;
834
+ brand: z.ZodOptional<z.ZodObject<{
835
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
836
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
837
+ }, z.core.$strict>>;
838
+ }, z.core.$strict>>;
663
839
  meta: z.ZodDefault<z.ZodObject<{
664
840
  organization: z.ZodOptional<z.ZodString>;
665
841
  authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -711,14 +887,18 @@ declare const PptxIRSchema: z.ZodObject<{
711
887
  br: "br";
712
888
  }>>;
713
889
  }, z.core.$strict>>;
890
+ seed: z.ZodOptional<z.ZodNumber>;
714
891
  slides: z.ZodArray<z.ZodObject<{
715
- type: z.ZodEnum<{
892
+ type: z.ZodDefault<z.ZodEnum<{
716
893
  cover: "cover";
717
894
  chapter: "chapter";
718
895
  content: "content";
719
896
  ending: "ending";
720
- }>;
721
- variant: z.ZodOptional<z.ZodEnum<{
897
+ }>>;
898
+ id: z.ZodOptional<z.ZodString>;
899
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
900
+ layout: z.ZodOptional<z.ZodString>;
901
+ arrangement: z.ZodOptional<z.ZodEnum<{
722
902
  code: "code";
723
903
  quote: "quote";
724
904
  single: "single";
@@ -727,15 +907,11 @@ declare const PptxIRSchema: z.ZodObject<{
727
907
  image_focus: "image_focus";
728
908
  big_number: "big_number";
729
909
  assertion_evidence: "assertion_evidence";
730
- image_split: "image_split";
731
- image_top: "image_top";
732
- image_bottom: "image_bottom";
733
- image_annotate: "image_annotate";
734
910
  aside: "aside";
735
911
  }>>;
736
912
  heading: z.ZodOptional<z.ZodString>;
737
913
  subheading: z.ZodOptional<z.ZodString>;
738
- blocks: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
914
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
739
915
  type: z.ZodLiteral<"bullets">;
740
916
  items: z.ZodArray<z.ZodString>;
741
917
  style: z.ZodOptional<z.ZodEnum<{
@@ -788,9 +964,9 @@ declare const PptxIRSchema: z.ZodObject<{
788
964
  chart_type: z.ZodEnum<{
789
965
  line: "line";
790
966
  dumbbell: "dumbbell";
967
+ funnel: "funnel";
791
968
  bar: "bar";
792
969
  pie: "pie";
793
- funnel: "funnel";
794
970
  }>;
795
971
  direction: z.ZodOptional<z.ZodEnum<{
796
972
  horizontal: "horizontal";
@@ -980,6 +1156,48 @@ declare const PptxIRSchema: z.ZodObject<{
980
1156
  vs: "vs";
981
1157
  before_after: "before_after";
982
1158
  }>>;
1159
+ }, z.core.$strict>, z.ZodObject<{
1160
+ type: z.ZodLiteral<"swot">;
1161
+ strengths: z.ZodArray<z.ZodString>;
1162
+ weaknesses: z.ZodArray<z.ZodString>;
1163
+ opportunities: z.ZodArray<z.ZodString>;
1164
+ threats: z.ZodArray<z.ZodString>;
1165
+ labels: z.ZodOptional<z.ZodObject<{
1166
+ strengths: z.ZodOptional<z.ZodString>;
1167
+ weaknesses: z.ZodOptional<z.ZodString>;
1168
+ opportunities: z.ZodOptional<z.ZodString>;
1169
+ threats: z.ZodOptional<z.ZodString>;
1170
+ }, z.core.$strict>>;
1171
+ }, z.core.$strict>, z.ZodObject<{
1172
+ type: z.ZodLiteral<"bmc">;
1173
+ key_partners: z.ZodArray<z.ZodString>;
1174
+ key_activities: z.ZodArray<z.ZodString>;
1175
+ key_resources: z.ZodArray<z.ZodString>;
1176
+ value_propositions: z.ZodArray<z.ZodString>;
1177
+ customer_relationships: z.ZodArray<z.ZodString>;
1178
+ channels: z.ZodArray<z.ZodString>;
1179
+ customer_segments: z.ZodArray<z.ZodString>;
1180
+ cost_structure: z.ZodArray<z.ZodString>;
1181
+ revenue_streams: z.ZodArray<z.ZodString>;
1182
+ }, z.core.$strict>, z.ZodObject<{
1183
+ type: z.ZodLiteral<"waterfall">;
1184
+ items: z.ZodArray<z.ZodObject<{
1185
+ label: z.ZodString;
1186
+ value: z.ZodNumber;
1187
+ kind: z.ZodOptional<z.ZodEnum<{
1188
+ delta: "delta";
1189
+ total: "total";
1190
+ }>>;
1191
+ }, z.core.$strict>>;
1192
+ unit: z.ZodOptional<z.ZodString>;
1193
+ }, z.core.$strict>, z.ZodObject<{
1194
+ type: z.ZodLiteral<"gantt">;
1195
+ items: z.ZodArray<z.ZodObject<{
1196
+ label: z.ZodString;
1197
+ start: z.ZodNumber;
1198
+ end: z.ZodNumber;
1199
+ }, z.core.$strict>>;
1200
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
983
1201
  }, z.core.$strict>], "type">>>;
984
1202
  background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
985
1203
  kind: z.ZodLiteral<"color">;
@@ -1024,10 +1242,11 @@ declare const PptxIRSchema: z.ZodObject<{
1024
1242
  right: "right";
1025
1243
  }>>;
1026
1244
  footnote: z.ZodOptional<z.ZodString>;
1245
+ notes: z.ZodOptional<z.ZodString>;
1027
1246
  }, z.core.$strict>>;
1028
1247
  }, z.core.$strict>;
1029
1248
  type PptxIR = z.infer<typeof PptxIRSchema>;
1030
- type Block = z.infer<typeof BlockSchema>;
1249
+ type Component = z.infer<typeof ComponentSchema>;
1031
1250
  type BackgroundSpec = z.infer<typeof BackgroundSpecSchema>;
1032
1251
  type Slide = z.infer<typeof SlideSchema>;
1033
1252
  type Assets = z.infer<typeof AssetsSchema>;
@@ -1038,39 +1257,1474 @@ interface ValidationIssue {
1038
1257
  message: string;
1039
1258
  /** 1-based slide number when the issue is scoped to a slide. */
1040
1259
  page?: number;
1260
+ /**
1261
+ * The offending slide's own `id` (`Slide.id`, `ir/index.ts`) — W5
1262
+ * whole-branch review finding 2: the README already claimed "validation
1263
+ * error messages reference [a slide] by [its] id"; this is what makes
1264
+ * that true. Set by every page-scoped issue producer
1265
+ * ({@link checkLayoutApplicability}, {@link checkBoundaryPageContent},
1266
+ * the content-quality-gate translation in {@link validateIr},
1267
+ * {@link checkDuplicateSlideIds})
1268
+ * when the slide in question has an `id` — absent when the slide has
1269
+ * none (bare, pre-W5 IR) or the issue is deck-level, not scoped to any
1270
+ * single slide. {@link formatIssues} appends it in parens after the page
1271
+ * number.
1272
+ */
1273
+ slideId?: string;
1041
1274
  }
1042
1275
  interface ValidateResult {
1043
1276
  ok: boolean;
1044
1277
  ir?: PptxIR;
1045
1278
  errors: ValidationIssue[];
1279
+ /**
1280
+ * Human-readable "`path`: `alias` → `canonical`" entries for every
1281
+ * deterministic field-alias rewrite `validateIr` applied before parsing
1282
+ * (W5 task 4, `ir/field-aliases.ts`'s `normalizeComponentAliases`) — e.g. a
1283
+ * kpi item's `title` silently adopted as `label`. Present only when at
1284
+ * least one rewrite happened; informational, never gates `ok` on its own.
1285
+ */
1286
+ normalized?: string[];
1046
1287
  }
1047
1288
  /**
1048
- * Validate raw JSON against the IR schema, then — once it parses — run the
1049
- * content-quality gate (`checkIrQuality`) against the parsed IR. Both stages
1050
- * must pass for `ok: true`. Quality findings are reported the same way as
1051
- * schema errors (page-scoped, 1-based). `checkIrQuality` itself tags findings
1052
- * "warn" vs "error", but that split was designed for its original consumer
1053
- * (surfacing informational warnings in a UI after generation, per its own
1054
- * docstring) here it is the pre-generation hard gate, so any finding blocks
1055
- * (`ok: false`), not only "error"-severity ones. Treating "warn" findings
1056
- * (e.g. a cover with no heading) as advisory-only would defeat the point of
1057
- * wiring this in: the spec's core principle is a hard protocol gate, not
1058
- * hoped-for prompt compliance.
1289
+ * Validate raw JSON against the IR schema, then — once it parses — resolve
1290
+ * `narrative` (`resolveNarrative`, spec §5: an unrecognized preset name is a
1291
+ * `narrative`-path error, page-less) and run the content-quality gate
1292
+ * (`checkIrQuality`, passed the resolved axes) against the parsed IR. All
1293
+ * stages must pass for `ok: true`. Quality findings are reported the same
1294
+ * way as schema errors (page-scoped, 1-based). `checkIrQuality` itself tags
1295
+ * findings "warn" vs "error", but that split was designed for its original
1296
+ * consumer (surfacing informational warnings in a UI after generation, per
1297
+ * its own docstring) here it is the pre-generation hard gate, so any
1298
+ * finding blocks (`ok: false`), not only "error"-severity ones. Treating
1299
+ * "warn" findings (e.g. a cover with no heading) as advisory-only would
1300
+ * defeat the point of wiring this in: the spec's core principle is a hard
1301
+ * protocol gate, not hoped-for prompt compliance.
1302
+ *
1303
+ * Before any of that, one deterministic alias pass runs
1304
+ * ({@link normalizeComponentAliases}, W5 task 4) for the component
1305
+ * field-name synonym rescue only (kpi `title`→`label`, quote
1306
+ * `content`→`text`, …) — a weak-model rescue for schema-internal synonym
1307
+ * drift, scoped to `slides[]`. It only rewrites where the canonical key is
1308
+ * absent, so the schema parse below never sees an alias as an "unrecognized
1309
+ * key" in the first place. Purely informational: every rewrite is recorded
1310
+ * as a human-readable `path: alias → canonical` string and threaded onto
1311
+ * `ValidateResult.normalized` on *every* return path below via
1312
+ * `withNormalized`, success or failure alike — it never itself gates `ok`.
1313
+ *
1314
+ * There is deliberately no root/narrative-level alias pass (spec §16,
1315
+ * reversing the now-superseded §15.4): a v4 document that still spells its
1316
+ * pre-rename vocabulary — `scenario` instead of `narrative`, `mode`/
1317
+ * `delivery` instead of `strategy`/`pacing`, or the old enum values
1318
+ * `"text"`/`"presentation"`/`"narrative"` — is not old-vocabulary
1319
+ * *compatibility*, it is exactly the vocabulary this rename retired, so it
1320
+ * hard-errors like any other unknown key or value: `scenario` fails the
1321
+ * schema's `.strict()` parse below as an unrecognized key, and an old enum
1322
+ * value (or the axis-key names `mode`/`delivery` inside `narrative`, which
1323
+ * the schema itself leaves open) fails `resolveNarrative`'s own runtime
1324
+ * check, listing the current values. `pptfast migrate` (`ir/migrate.ts`)
1325
+ * remains the sanctioned bridge for a genuine v3 document — see the v3 hard
1326
+ * reject below, which points there.
1327
+ *
1328
+ * The component-alias pass only ever runs for a document already headed for
1329
+ * the v4 schema — an explicit `version: "2"` or `version: "3"` is
1330
+ * hard-rejected first, below, before the alias pass or any schema parse
1331
+ * (spec §9.3: a v2/v3 document is never silently reinterpreted as v4).
1059
1332
  */
1060
1333
  declare function validateIr(input: unknown): ValidateResult;
1334
+ /**
1335
+ * `"page 2 (p-kpi) — path: message"` when the issue carries both a `page`
1336
+ * and a `slideId` (W5 whole-branch review finding 2 — the README's own
1337
+ * claim that a validation error "references [a slide] by [its] id", made
1338
+ * true) — the parenthesized id is appended only alongside a `page` number,
1339
+ * never on its own: a deck-level issue that happens to set a representative
1340
+ * `slideId` with no `page` ({@link checkDuplicateSlideIds} above) keeps its
1341
+ * pre-existing, unchanged format. Every other combination — `page` with no
1342
+ * `slideId` (an id-less slide), or neither — is byte-identical to before
1343
+ * this task.
1344
+ */
1061
1345
  declare function formatIssues(errors: ValidationIssue[]): string;
1062
1346
  /** Render a single slide to standalone SVG markup (preview / self-check). */
1063
1347
  declare function renderSlideSvg(ir: PptxIR, slideIndex: number): string;
1064
1348
  /** Full pipeline: validate → SVG → DrawingML → animation patches → pptx bytes. */
1065
- declare function generatePptx(input: unknown): Promise<Uint8Array>;
1349
+ declare function generatePptx(input: unknown, opts?: {
1350
+ draft?: boolean;
1351
+ }): Promise<Uint8Array>;
1066
1352
  interface ThemeInfo {
1067
1353
  id: string;
1068
1354
  label: string;
1069
1355
  colors: Record<string, unknown>;
1070
1356
  }
1071
- /** Built-in theme catalog with labels and color tokens. */
1357
+ /** Built-in theme catalog with labels and style color tokens. */
1072
1358
  declare function listThemes(): ThemeInfo[];
1073
1359
  /** JSON Schema for the IR — feed this to a model before it writes IR. */
1074
1360
  declare function irJsonSchema(): Record<string, unknown>;
1361
+ /** JSON Schema for style-token overrides (IR theme.style, --style files, config "style"). */
1362
+ declare function styleJsonSchema(): Record<string, unknown>;
1363
+
1364
+ /**
1365
+ * The frozen IR v3 top-level shape (vocabulary-v4 rename, task 1 — spec
1366
+ * §9.3: "v3 已冻结... 顶层字段和枚举改名必须进入新的 IR 版本,不能在 v3
1367
+ * 内静默改变含义"). `./index.ts`'s `PptxIRSchema` is v4 now — this module
1368
+ * exists only so a genuinely v3-shaped document still has somewhere to parse
1369
+ * against: `migrateIrV3ToV4`'s input type (`./migrate.ts`), and the
1370
+ * v3-hard-reject path's own tests (constructing a *valid* v3 IR to prove the
1371
+ * reject fires on version alone, not on some other schema defect).
1372
+ *
1373
+ * `validateIr` (`src/api.ts`) never calls this schema itself — an incoming
1374
+ * v3 document (`version === "3"`) is hard-rejected before any schema parse
1375
+ * runs at all (spec §9.3), full stop. This schema is a migration-tooling and
1376
+ * test fixture, not a second accepted input shape.
1377
+ *
1378
+ * Every field but `version` and `scenario` is byte-identical to `./index.ts`'s
1379
+ * v4 `PptxIRSchema` (spec §9.1: "其余 IR 字段保持不变") — reuses the exact
1380
+ * same `ThemeSchema`/`MetaSchema`/`AssetsSchema`/`BrandSchema`/`SlideSchema`
1381
+ * instances rather than redefining them, so there is no way for this frozen
1382
+ * shape to silently drift from the fields it shares with v4.
1383
+ */
1384
+ declare const PptxIRV3Schema: z.ZodObject<{
1385
+ version: z.ZodDefault<z.ZodLiteral<"3">>;
1386
+ filename: z.ZodDefault<z.ZodString>;
1387
+ scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1388
+ theme: z.ZodDefault<z.ZodObject<{
1389
+ id: z.ZodDefault<z.ZodString>;
1390
+ style: z.ZodOptional<z.ZodObject<{
1391
+ colors: z.ZodOptional<z.ZodObject<{
1392
+ bg: z.ZodOptional<z.ZodString>;
1393
+ surface: z.ZodOptional<z.ZodString>;
1394
+ panel: z.ZodOptional<z.ZodString>;
1395
+ primary: z.ZodOptional<z.ZodString>;
1396
+ accent: z.ZodOptional<z.ZodString>;
1397
+ text: z.ZodOptional<z.ZodString>;
1398
+ muted: z.ZodOptional<z.ZodString>;
1399
+ border: z.ZodOptional<z.ZodString>;
1400
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
1401
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
1402
+ cardStroke: z.ZodOptional<z.ZodString>;
1403
+ }, z.core.$strict>>;
1404
+ fonts: z.ZodOptional<z.ZodObject<{
1405
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
1406
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
1407
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
1408
+ }, z.core.$strict>>;
1409
+ shape: z.ZodOptional<z.ZodObject<{
1410
+ radius: z.ZodOptional<z.ZodNumber>;
1411
+ gapScale: z.ZodOptional<z.ZodNumber>;
1412
+ }, z.core.$strict>>;
1413
+ }, z.core.$strict>>;
1414
+ brand: z.ZodOptional<z.ZodObject<{
1415
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
1416
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
1417
+ }, z.core.$strict>>;
1418
+ }, z.core.$strict>>;
1419
+ meta: z.ZodDefault<z.ZodObject<{
1420
+ organization: z.ZodOptional<z.ZodString>;
1421
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
1422
+ name: z.ZodString;
1423
+ role: z.ZodOptional<z.ZodString>;
1424
+ org: z.ZodOptional<z.ZodString>;
1425
+ }, z.core.$strict>>>;
1426
+ date: z.ZodOptional<z.ZodString>;
1427
+ version: z.ZodOptional<z.ZodString>;
1428
+ confidentiality: z.ZodOptional<z.ZodEnum<{
1429
+ public: "public";
1430
+ internal: "internal";
1431
+ confidential: "confidential";
1432
+ restricted: "restricted";
1433
+ }>>;
1434
+ contact: z.ZodOptional<z.ZodObject<{
1435
+ name: z.ZodOptional<z.ZodString>;
1436
+ email: z.ZodOptional<z.ZodString>;
1437
+ phone: z.ZodOptional<z.ZodString>;
1438
+ website: z.ZodOptional<z.ZodString>;
1439
+ }, z.core.$strict>>;
1440
+ copyright: z.ZodOptional<z.ZodString>;
1441
+ animation: z.ZodOptional<z.ZodObject<{
1442
+ transition: z.ZodOptional<z.ZodEnum<{
1443
+ fade: "fade";
1444
+ push: "push";
1445
+ wipe: "wipe";
1446
+ none: "none";
1447
+ }>>;
1448
+ elements: z.ZodOptional<z.ZodEnum<{
1449
+ none: "none";
1450
+ auto: "auto";
1451
+ }>>;
1452
+ }, z.core.$strict>>;
1453
+ }, z.core.$strict>>;
1454
+ assets: z.ZodDefault<z.ZodObject<{
1455
+ images: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1456
+ src: z.ZodString;
1457
+ alt: z.ZodOptional<z.ZodString>;
1458
+ error: z.ZodOptional<z.ZodString>;
1459
+ }, z.core.$strict>>>;
1460
+ }, z.core.$strict>>;
1461
+ brand: z.ZodOptional<z.ZodObject<{
1462
+ logo_asset_id: z.ZodOptional<z.ZodString>;
1463
+ position: z.ZodOptional<z.ZodEnum<{
1464
+ tl: "tl";
1465
+ tr: "tr";
1466
+ bl: "bl";
1467
+ br: "br";
1468
+ }>>;
1469
+ }, z.core.$strict>>;
1470
+ seed: z.ZodOptional<z.ZodNumber>;
1471
+ slides: z.ZodArray<z.ZodObject<{
1472
+ type: z.ZodDefault<z.ZodEnum<{
1473
+ cover: "cover";
1474
+ chapter: "chapter";
1475
+ content: "content";
1476
+ ending: "ending";
1477
+ }>>;
1478
+ id: z.ZodOptional<z.ZodString>;
1479
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
1480
+ layout: z.ZodOptional<z.ZodString>;
1481
+ arrangement: z.ZodOptional<z.ZodEnum<{
1482
+ code: "code";
1483
+ quote: "quote";
1484
+ single: "single";
1485
+ two_column: "two_column";
1486
+ kpi_focus: "kpi_focus";
1487
+ image_focus: "image_focus";
1488
+ big_number: "big_number";
1489
+ assertion_evidence: "assertion_evidence";
1490
+ aside: "aside";
1491
+ }>>;
1492
+ heading: z.ZodOptional<z.ZodString>;
1493
+ subheading: z.ZodOptional<z.ZodString>;
1494
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1495
+ type: z.ZodLiteral<"bullets">;
1496
+ items: z.ZodArray<z.ZodString>;
1497
+ style: z.ZodOptional<z.ZodEnum<{
1498
+ default: "default";
1499
+ checklist: "checklist";
1500
+ numbered: "numbered";
1501
+ plain: "plain";
1502
+ divided: "divided";
1503
+ }>>;
1504
+ }, z.core.$strict>, z.ZodObject<{
1505
+ type: z.ZodLiteral<"paragraph">;
1506
+ text: z.ZodString;
1507
+ }, z.core.$strict>, z.ZodObject<{
1508
+ type: z.ZodLiteral<"quote">;
1509
+ text: z.ZodString;
1510
+ attribution: z.ZodOptional<z.ZodString>;
1511
+ }, z.core.$strict>, z.ZodObject<{
1512
+ type: z.ZodLiteral<"callout">;
1513
+ variant: z.ZodEnum<{
1514
+ info: "info";
1515
+ warn: "warn";
1516
+ tip: "tip";
1517
+ }>;
1518
+ text: z.ZodString;
1519
+ icon: z.ZodOptional<z.ZodEnum<{
1520
+ [x: string]: string;
1521
+ }>>;
1522
+ }, z.core.$strict>, z.ZodObject<{
1523
+ type: z.ZodLiteral<"code">;
1524
+ language: z.ZodString;
1525
+ code: z.ZodString;
1526
+ }, z.core.$strict>, z.ZodObject<{
1527
+ type: z.ZodLiteral<"kpi_cards">;
1528
+ items: z.ZodArray<z.ZodObject<{
1529
+ value: z.ZodString;
1530
+ unit: z.ZodOptional<z.ZodString>;
1531
+ label: z.ZodString;
1532
+ delta: z.ZodOptional<z.ZodEnum<{
1533
+ up: "up";
1534
+ down: "down";
1535
+ flat: "flat";
1536
+ }>>;
1537
+ icon: z.ZodOptional<z.ZodEnum<{
1538
+ [x: string]: string;
1539
+ }>>;
1540
+ source: z.ZodOptional<z.ZodString>;
1541
+ }, z.core.$strict>>;
1542
+ }, z.core.$strict>, z.ZodObject<{
1543
+ type: z.ZodLiteral<"chart">;
1544
+ chart_type: z.ZodEnum<{
1545
+ line: "line";
1546
+ dumbbell: "dumbbell";
1547
+ funnel: "funnel";
1548
+ bar: "bar";
1549
+ pie: "pie";
1550
+ }>;
1551
+ direction: z.ZodOptional<z.ZodEnum<{
1552
+ horizontal: "horizontal";
1553
+ vertical: "vertical";
1554
+ }>>;
1555
+ style: z.ZodOptional<z.ZodEnum<{
1556
+ donut: "donut";
1557
+ }>>;
1558
+ axes: z.ZodOptional<z.ZodObject<{
1559
+ x_title: z.ZodOptional<z.ZodString>;
1560
+ y_title: z.ZodOptional<z.ZodString>;
1561
+ show_grid: z.ZodOptional<z.ZodBoolean>;
1562
+ }, z.core.$strict>>;
1563
+ series: z.ZodArray<z.ZodObject<{
1564
+ name: z.ZodString;
1565
+ data: z.ZodArray<z.ZodObject<{
1566
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1567
+ y: z.ZodNumber;
1568
+ }, z.core.$strict>>;
1569
+ }, z.core.$strict>>;
1570
+ }, z.core.$strict>, z.ZodObject<{
1571
+ type: z.ZodLiteral<"flowchart">;
1572
+ nodes: z.ZodArray<z.ZodObject<{
1573
+ id: z.ZodString;
1574
+ label: z.ZodString;
1575
+ kind: z.ZodOptional<z.ZodEnum<{
1576
+ rect: "rect";
1577
+ diamond: "diamond";
1578
+ round: "round";
1579
+ }>>;
1580
+ }, z.core.$strict>>;
1581
+ edges: z.ZodArray<z.ZodObject<{
1582
+ from: z.ZodString;
1583
+ to: z.ZodString;
1584
+ label: z.ZodOptional<z.ZodString>;
1585
+ }, z.core.$strict>>;
1586
+ direction: z.ZodOptional<z.ZodEnum<{
1587
+ TB: "TB";
1588
+ TD: "TD";
1589
+ BT: "BT";
1590
+ LR: "LR";
1591
+ RL: "RL";
1592
+ }>>;
1593
+ }, z.core.$strict>, z.ZodObject<{
1594
+ type: z.ZodLiteral<"architecture">;
1595
+ layers: z.ZodArray<z.ZodObject<{
1596
+ title: z.ZodString;
1597
+ items: z.ZodArray<z.ZodString>;
1598
+ }, z.core.$strict>>;
1599
+ }, z.core.$strict>, z.ZodObject<{
1600
+ type: z.ZodLiteral<"timeline">;
1601
+ layout: z.ZodOptional<z.ZodEnum<{
1602
+ horizontal: "horizontal";
1603
+ vertical: "vertical";
1604
+ }>>;
1605
+ milestones: z.ZodArray<z.ZodObject<{
1606
+ date: z.ZodString;
1607
+ title: z.ZodString;
1608
+ desc: z.ZodOptional<z.ZodString>;
1609
+ highlight: z.ZodOptional<z.ZodBoolean>;
1610
+ }, z.core.$strict>>;
1611
+ }, z.core.$strict>, z.ZodObject<{
1612
+ type: z.ZodLiteral<"comparison">;
1613
+ columns: z.ZodArray<z.ZodString>;
1614
+ rows: z.ZodArray<z.ZodObject<{
1615
+ label: z.ZodString;
1616
+ cells: z.ZodArray<z.ZodString>;
1617
+ }, z.core.$strict>>;
1618
+ }, z.core.$strict>, z.ZodObject<{
1619
+ type: z.ZodLiteral<"icon_cards">;
1620
+ items: z.ZodArray<z.ZodObject<{
1621
+ icon: z.ZodEnum<{
1622
+ [x: string]: string;
1623
+ }>;
1624
+ title: z.ZodString;
1625
+ text: z.ZodString;
1626
+ }, z.core.$strict>>;
1627
+ }, z.core.$strict>, z.ZodObject<{
1628
+ type: z.ZodLiteral<"row_cards">;
1629
+ items: z.ZodArray<z.ZodObject<{
1630
+ icon: z.ZodOptional<z.ZodEnum<{
1631
+ [x: string]: string;
1632
+ }>>;
1633
+ title: z.ZodString;
1634
+ text: z.ZodOptional<z.ZodString>;
1635
+ sub: z.ZodOptional<z.ZodString>;
1636
+ highlight: z.ZodOptional<z.ZodBoolean>;
1637
+ }, z.core.$strict>>;
1638
+ }, z.core.$strict>, z.ZodObject<{
1639
+ type: z.ZodLiteral<"steps">;
1640
+ items: z.ZodArray<z.ZodObject<{
1641
+ title: z.ZodString;
1642
+ text: z.ZodString;
1643
+ }, z.core.$strict>>;
1644
+ }, z.core.$strict>, z.ZodObject<{
1645
+ type: z.ZodLiteral<"rings">;
1646
+ items: z.ZodArray<z.ZodObject<{
1647
+ label: z.ZodString;
1648
+ desc: z.ZodOptional<z.ZodString>;
1649
+ }, z.core.$strict>>;
1650
+ }, z.core.$strict>, z.ZodObject<{
1651
+ type: z.ZodLiteral<"numbered_cards">;
1652
+ items: z.ZodArray<z.ZodObject<{
1653
+ title: z.ZodString;
1654
+ text: z.ZodOptional<z.ZodString>;
1655
+ sub: z.ZodOptional<z.ZodString>;
1656
+ }, z.core.$strict>>;
1657
+ }, z.core.$strict>, z.ZodObject<{
1658
+ type: z.ZodLiteral<"roadmap">;
1659
+ items: z.ZodArray<z.ZodObject<{
1660
+ title: z.ZodString;
1661
+ period: z.ZodOptional<z.ZodString>;
1662
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
1663
+ label: z.ZodString;
1664
+ value: z.ZodString;
1665
+ }, z.core.$strict>>>;
1666
+ }, z.core.$strict>>;
1667
+ }, z.core.$strict>, z.ZodObject<{
1668
+ type: z.ZodLiteral<"matrix">;
1669
+ x_title: z.ZodOptional<z.ZodString>;
1670
+ y_title: z.ZodOptional<z.ZodString>;
1671
+ cols: z.ZodNumber;
1672
+ items: z.ZodArray<z.ZodObject<{
1673
+ title: z.ZodString;
1674
+ tag: z.ZodOptional<z.ZodString>;
1675
+ tone: z.ZodOptional<z.ZodEnum<{
1676
+ info: "info";
1677
+ accent: "accent";
1678
+ neutral: "neutral";
1679
+ }>>;
1680
+ }, z.core.$strict>>;
1681
+ }, z.core.$strict>, z.ZodObject<{
1682
+ type: z.ZodLiteral<"insight_panel">;
1683
+ title: z.ZodString;
1684
+ rows: z.ZodArray<z.ZodObject<{
1685
+ label: z.ZodString;
1686
+ text: z.ZodString;
1687
+ }, z.core.$strict>>;
1688
+ footnote: z.ZodOptional<z.ZodString>;
1689
+ }, z.core.$strict>, z.ZodObject<{
1690
+ type: z.ZodLiteral<"verdict_banner">;
1691
+ text: z.ZodString;
1692
+ tone: z.ZodEnum<{
1693
+ neutral: "neutral";
1694
+ positive: "positive";
1695
+ warning: "warning";
1696
+ }>;
1697
+ icon: z.ZodOptional<z.ZodEnum<{
1698
+ [x: string]: string;
1699
+ }>>;
1700
+ }, z.core.$strict>, z.ZodObject<{
1701
+ type: z.ZodLiteral<"citation">;
1702
+ sources: z.ZodArray<z.ZodObject<{
1703
+ label: z.ZodString;
1704
+ url: z.ZodOptional<z.ZodString>;
1705
+ ref: z.ZodOptional<z.ZodString>;
1706
+ }, z.core.$strict>>;
1707
+ }, z.core.$strict>, z.ZodObject<{
1708
+ type: z.ZodLiteral<"image">;
1709
+ asset_id: z.ZodString;
1710
+ caption: z.ZodOptional<z.ZodString>;
1711
+ fit: z.ZodDefault<z.ZodEnum<{
1712
+ cover: "cover";
1713
+ contain: "contain";
1714
+ }>>;
1715
+ }, z.core.$strict>, z.ZodObject<{
1716
+ type: z.ZodLiteral<"image_grid">;
1717
+ items: z.ZodArray<z.ZodObject<{
1718
+ asset_id: z.ZodString;
1719
+ caption: z.ZodOptional<z.ZodString>;
1720
+ }, z.core.$strict>>;
1721
+ emphasis: z.ZodOptional<z.ZodEnum<{
1722
+ none: "none";
1723
+ first: "first";
1724
+ }>>;
1725
+ }, z.core.$strict>, z.ZodObject<{
1726
+ type: z.ZodLiteral<"image_compare">;
1727
+ left: z.ZodObject<{
1728
+ asset_id: z.ZodString;
1729
+ label: z.ZodString;
1730
+ }, z.core.$strict>;
1731
+ right: z.ZodObject<{
1732
+ asset_id: z.ZodString;
1733
+ label: z.ZodString;
1734
+ }, z.core.$strict>;
1735
+ style: z.ZodOptional<z.ZodEnum<{
1736
+ vs: "vs";
1737
+ before_after: "before_after";
1738
+ }>>;
1739
+ }, z.core.$strict>, z.ZodObject<{
1740
+ type: z.ZodLiteral<"swot">;
1741
+ strengths: z.ZodArray<z.ZodString>;
1742
+ weaknesses: z.ZodArray<z.ZodString>;
1743
+ opportunities: z.ZodArray<z.ZodString>;
1744
+ threats: z.ZodArray<z.ZodString>;
1745
+ labels: z.ZodOptional<z.ZodObject<{
1746
+ strengths: z.ZodOptional<z.ZodString>;
1747
+ weaknesses: z.ZodOptional<z.ZodString>;
1748
+ opportunities: z.ZodOptional<z.ZodString>;
1749
+ threats: z.ZodOptional<z.ZodString>;
1750
+ }, z.core.$strict>>;
1751
+ }, z.core.$strict>, z.ZodObject<{
1752
+ type: z.ZodLiteral<"bmc">;
1753
+ key_partners: z.ZodArray<z.ZodString>;
1754
+ key_activities: z.ZodArray<z.ZodString>;
1755
+ key_resources: z.ZodArray<z.ZodString>;
1756
+ value_propositions: z.ZodArray<z.ZodString>;
1757
+ customer_relationships: z.ZodArray<z.ZodString>;
1758
+ channels: z.ZodArray<z.ZodString>;
1759
+ customer_segments: z.ZodArray<z.ZodString>;
1760
+ cost_structure: z.ZodArray<z.ZodString>;
1761
+ revenue_streams: z.ZodArray<z.ZodString>;
1762
+ }, z.core.$strict>, z.ZodObject<{
1763
+ type: z.ZodLiteral<"waterfall">;
1764
+ items: z.ZodArray<z.ZodObject<{
1765
+ label: z.ZodString;
1766
+ value: z.ZodNumber;
1767
+ kind: z.ZodOptional<z.ZodEnum<{
1768
+ delta: "delta";
1769
+ total: "total";
1770
+ }>>;
1771
+ }, z.core.$strict>>;
1772
+ unit: z.ZodOptional<z.ZodString>;
1773
+ }, z.core.$strict>, z.ZodObject<{
1774
+ type: z.ZodLiteral<"gantt">;
1775
+ items: z.ZodArray<z.ZodObject<{
1776
+ label: z.ZodString;
1777
+ start: z.ZodNumber;
1778
+ end: z.ZodNumber;
1779
+ }, z.core.$strict>>;
1780
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1781
+ }, z.core.$strict>], "type">>>;
1782
+ background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1783
+ kind: z.ZodLiteral<"color">;
1784
+ value: z.ZodString;
1785
+ }, z.core.$strict>, z.ZodObject<{
1786
+ kind: z.ZodLiteral<"gradient">;
1787
+ from: z.ZodString;
1788
+ to: z.ZodString;
1789
+ direction: z.ZodOptional<z.ZodEnum<{
1790
+ tb: "tb";
1791
+ lr: "lr";
1792
+ diagonal: "diagonal";
1793
+ }>>;
1794
+ }, z.core.$strict>, z.ZodObject<{
1795
+ kind: z.ZodLiteral<"asset">;
1796
+ asset_id: z.ZodString;
1797
+ overlay: z.ZodOptional<z.ZodObject<{
1798
+ color: z.ZodString;
1799
+ opacity: z.ZodNumber;
1800
+ }, z.core.$strict>>;
1801
+ fit: z.ZodOptional<z.ZodEnum<{
1802
+ cover: "cover";
1803
+ contain: "contain";
1804
+ }>>;
1805
+ }, z.core.$strict>], "kind">>;
1806
+ decor: z.ZodOptional<z.ZodObject<{
1807
+ kind: z.ZodEnum<{
1808
+ big_number: "big_number";
1809
+ corner_tag: "corner_tag";
1810
+ rule_line: "rule_line";
1811
+ quote_marks: "quote_marks";
1812
+ geo_dots: "geo_dots";
1813
+ }>;
1814
+ intensity: z.ZodOptional<z.ZodEnum<{
1815
+ subtle: "subtle";
1816
+ normal: "normal";
1817
+ }>>;
1818
+ text: z.ZodOptional<z.ZodString>;
1819
+ }, z.core.$strict>>;
1820
+ image_side: z.ZodOptional<z.ZodEnum<{
1821
+ left: "left";
1822
+ right: "right";
1823
+ }>>;
1824
+ footnote: z.ZodOptional<z.ZodString>;
1825
+ notes: z.ZodOptional<z.ZodString>;
1826
+ }, z.core.$strict>>;
1827
+ }, z.core.$strict>;
1828
+ type PptxIRV3 = z.infer<typeof PptxIRV3Schema>;
1829
+
1830
+ /**
1831
+ * Deterministic, pure IR v3 → v4 migration (spec §9.1). Field-for-field,
1832
+ * value-for-value per the mapping in {@link migrateNarrativeInput}'s
1833
+ * docstring — every field this function doesn't touch (`filename`, `theme`,
1834
+ * `meta`, `assets`, `brand`, `seed`, `slides`) carries across by the exact
1835
+ * same reference it came in with, unchanged (spec §9.1: "其余 IR 字段保持不
1836
+ * 变"; spec §10: no weight/budget/selection/render change is in scope for
1837
+ * this migration, ever).
1838
+ *
1839
+ * Exported from the SDK surface (`src/index.ts`) as the deterministic
1840
+ * migration primitive the `pptfast migrate` CLI command (task 2) wraps —
1841
+ * this function itself does no I/O and never runs a model, per spec §9.3's
1842
+ * "只做已声明的结构映射,不运行模型,不重写内容,不重新选择 layout".
1843
+ *
1844
+ * Takes an already-parsed `PptxIRV3` (i.e. `PptxIRV3Schema.parse(...)`'s
1845
+ * output, defaults already applied) rather than raw `unknown` JSON — schema
1846
+ * validation of the v3 input is the caller's job (the CLI parses-then-
1847
+ * migrates; `validateIr`'s own v3 path hard-rejects before ever reaching
1848
+ * this function, spec §9.3, so `validateIr` itself never calls this).
1849
+ */
1850
+ declare function migrateIrV3ToV4(v3: PptxIRV3): PptxIR;
1851
+
1852
+ type LayoutType = "cover" | "chapter" | "content" | "ending";
1853
+ interface StyleColors {
1854
+ bg: string;
1855
+ surface: string;
1856
+ panel?: string;
1857
+ primary: string;
1858
+ accent: string;
1859
+ text: string;
1860
+ muted: string;
1861
+ border?: string;
1862
+ chartPalette: string[];
1863
+ /**
1864
+ * Optional accent color pool for themes whose layout grammar cycles through
1865
+ * multiple accent colors (e.g. tech card corner stripes). Themes that
1866
+ * only use a single accent color can omit this.
1867
+ */
1868
+ accentPool?: string[];
1869
+ /**
1870
+ * Optional hairline stroke color for the shared "surface card" shells
1871
+ * (kpi_cards / icon_cards / callout — see components/kpi.tsx,
1872
+ * components/icon-cards.tsx, components/callout.tsx). Those component renderers are
1873
+ * shared across every theme and must stay theme-agnostic (no
1874
+ * `if (themeId === ...)` branching), so a theme that wants its cards
1875
+ * outlined sets this token instead — the renderers just consult it and add
1876
+ * a 1px stroke when it's defined. Omitted (`undefined`, the default) draws
1877
+ * no stroke, so every theme that doesn't set it keeps its current
1878
+ * fill-only card unchanged.
1879
+ */
1880
+ cardStroke?: string;
1881
+ }
1882
+ interface StyleFonts {
1883
+ heading: string[];
1884
+ body: string[];
1885
+ mono?: string[];
1886
+ }
1887
+ interface StyleTokens {
1888
+ id: string;
1889
+ allowCustomBackground?: boolean;
1890
+ colors: StyleColors;
1891
+ fonts: StyleFonts;
1892
+ shape?: StyleShape;
1893
+ defaultBackgrounds: Record<LayoutType, BackgroundSpec>;
1894
+ }
1895
+ /**
1896
+ * 主题细节 shape token(2026-07-10 用户立项,spec:
1897
+ * .issues/specs/2026-07-10-pptx-theme-detail-tokens-design.md)。
1898
+ * 全部可选——缺省时各消费点沿用自己的 baked 值(零观感)。
1899
+ */
1900
+ interface StyleShape {
1901
+ /** 卡片/横幅圆角 px(统一值)。缺省=各消费点原 baked 值(kpi 8/architecture 6/白卡 14 等)。 */
1902
+ radius?: number;
1903
+ /** 块间距缩放(1=现 BLOCK_GAP)。建议范围 [0.8, 1.3]。 */
1904
+ gapScale?: number;
1905
+ }
1906
+
1907
+ type MotifArchetypeId = "banner-motif" | "rail-motif" | "poster-motif" | "constellation-motif" | "corner-ornament-motif" | "tone-adaptive-motif" | "campaign-motif" | "bloom-motif" | "classroom-motif" | "ink-motif" | "luxe-motif" | "enterprise-motif" | "heritage-motif";
1908
+
1909
+ /**
1910
+ * A theme = distributable bundle: `style` (style tokens) + `brand` (brand
1911
+ * chrome) + affinity tags (filled in W4).
1912
+ *
1913
+ * `id` is a plain `string`, not `CanonicalThemeId` — the 13 builtins satisfy
1914
+ * this (`CanonicalThemeId` is a subtype of `string`), but `registerTheme`
1915
+ * below (W3 task 4's SDK registration seam) must also accept ids outside that
1916
+ * closed union.
1917
+ */
1918
+ interface ThemeDefinition {
1919
+ id: string;
1920
+ style: StyleTokens;
1921
+ brand: BrandConfig;
1922
+ tags: readonly string[];
1923
+ /**
1924
+ * 主题的「选择权」配置(spec §3 theme.layouts 命名裁决;W2 任务 2 由
1925
+ * src/themes/manifest.ts〔已删除〕的旧选择权类型原地迁居于此)——四页型
1926
+ * 各自允许哪些 archetype 参与自动选型。排印/色彩在 style,这里只放集合。
1927
+ * **W4 全集放开**(spec §3「缺省 = 全集,策展收窄塑造个性」,design
1928
+ * decision 7):十三内置主题四页型默认均为 {@link fullArchetypeSet} 的
1929
+ * 全集。design decision 7/8 曾经的六处对比度策展排除(luxe/campaign/
1930
+ * classroom 的 content 排除 banner-heading、tech 的 cover/content、
1931
+ * consulting 的 chapter)已在 W4 fix round 随对比度自适应 ink helper
1932
+ * (`src/svg/ink.ts`)的根因修复全部撤销。fix round 自身新发现的三处
1933
+ * (bloom/classroom/heritage 的 chapter 排除 fashion-chapter)也已在
1934
+ * post-v0.3 W8 fix round 随 `readableOn` 两墨实测对比度取优的根因修复一并
1935
+ * 撤销(backlog item 2)——十三主题四页型现在均为不折不扣的全集,无任何
1936
+ * 排除残留。页型空集 = 该页型回落调用侧兜底(十三主题四页型均非空,
1937
+ * `definitions.test.ts` 锁死)。id 是通用 string(不再按页型区分
1938
+ * archetype id 联合类型)。
1939
+ */
1940
+ layouts: Record<Slide["type"], readonly string[]>;
1941
+ /** Motif:单值,非 allowed-set(spec §3 示意)。undefined = 该主题无 motif 装饰(十三主题中 runway 留空,其余均已设)。 */
1942
+ motif?: MotifArchetypeId;
1943
+ }
1944
+ /**
1945
+ * `registerTheme`'s input shape (W4, spec §3 "缺省 = 全集"): identical to
1946
+ * {@link ThemeDefinition} except `layouts` is optional, and — when present —
1947
+ * each of its four slide-type entries is independently optional too. A
1948
+ * slide type this theme doesn't narrow (its own key omitted, or the whole
1949
+ * `layouts` object omitted) defaults to that type's full registered-
1950
+ * archetype set ({@link FULL_LAYOUTS}) — the exact same default every
1951
+ * builtin theme in `LAYOUTS` above resolves to for a slide type it doesn't
1952
+ * curate away from. `getThemeDefinition`/`REGISTERED_THEMES` still only ever
1953
+ * hold the fully-resolved `ThemeDefinition` shape (`layouts` total over all
1954
+ * four types) — `registerTheme` performs the defaulting once, here, so
1955
+ * every downstream reader (`resolveArchetypeId` foremost) can keep assuming
1956
+ * a total record and never re-derive "was this slide type curated or
1957
+ * defaulted".
1958
+ */
1959
+ type ThemeRegistration = Omit<ThemeDefinition, "layouts"> & {
1960
+ layouts?: Partial<Record<Slide["type"], readonly string[]>>;
1961
+ };
1962
+ /**
1963
+ * Register a theme at runtime (SDK seam, not the v0.4 distribution
1964
+ * protocol). Validates just enough to keep the render chain from silently
1965
+ * breaking on a malformed registration — not a full schema:
1966
+ *
1967
+ * - `id` must not collide with a builtin or an already-registered theme.
1968
+ * - each of the four slide types, once defaulted ({@link ThemeRegistration}),
1969
+ * must have at least one layout id that is both registered in
1970
+ * `LAYOUT_REGISTRY` and valid for that slide type (the same registry
1971
+ * `resolveArchetypeId`/`FullSlideSvg` select from — a theme never ships
1972
+ * new render code, only a curated subset of the existing 30 archetypes +
1973
+ * 4 takeovers, per `docs/architecture.md`'s "Adding a theme" section). An
1974
+ * *explicit* empty array for a slide type still fails this check (the
1975
+ * default only kicks in when the key — or `layouts` itself — is omitted
1976
+ * entirely, `undefined`, never for a caller-supplied `[]`).
1977
+ * - `style` must be present (a JS caller can bypass the TS type).
1978
+ *
1979
+ * Once registered, the theme participates in `getInstalledThemeIds`,
1980
+ * `getThemeDefinition` (hence `effective-layout.ts`/`FullSlideSvg`'s
1981
+ * selection and `resolveBrand`), and `themes/index.ts`'s `resolveStyle` —
1982
+ * every internal theme lookup, with no separate "registered theme" branch
1983
+ * for callers to remember.
1984
+ */
1985
+ declare function registerTheme(def: ThemeRegistration): void;
1986
+ /** Every installed theme id: the 13 builtins, then registered themes in registration order. */
1987
+ declare function getInstalledThemeIds(): readonly string[];
1988
+ /**
1989
+ * Resolve a theme id to its full definition — a registered theme first, then
1990
+ * the builtin fallback (`THEME_DEFINITIONS[resolveThemeId(id)]`, which itself
1991
+ * folds an unrecognized id to consulting). The one lookup every internal
1992
+ * consumer that used to read `THEME_DEFINITIONS[resolveThemeId(id)]`
1993
+ * directly (`effective-layout.ts`, `FullSlideSvg.tsx`) now calls instead, so
1994
+ * a registered theme's curated layouts actually drive selection end-to-end.
1995
+ */
1996
+ declare function getThemeDefinition(id: string): ThemeDefinition;
1997
+
1998
+ declare const STRATEGY_VALUES: readonly ["pyramid", "storytelling", "instructional", "showcase", "briefing"];
1999
+ declare const PACING_VALUES: readonly ["dense", "balanced", "spacious"];
2000
+ declare const AUDIENCE_VALUES: readonly ["executive", "technical", "customer", "public"];
2001
+
2002
+ /**
2003
+ * Narrative argument style (spec §5's five-way strategy classification,
2004
+ * renamed from "mode" — spec §8.1). Each strategy carries a
2005
+ * component/layout tendency set (soft-weight material, consumed by W4's
2006
+ * weighted selection — nothing consumes it this wave) and a beat policy
2007
+ * (renamed from "rhythm policy" — spec §2.3's deck/page beat split; consumed
2008
+ * by W5's spec-validate rotation gate).
2009
+ */
2010
+ type Strategy = (typeof STRATEGY_VALUES)[number];
2011
+ /**
2012
+ * Content density budget + typographic baseline (spec §5 pacing table,
2013
+ * renamed from "delivery" — spec §8.1). See {@link PacingBudget} for the
2014
+ * dual-attribute capacity split this axis is one half of.
2015
+ */
2016
+ type Pacing = (typeof PACING_VALUES)[number];
2017
+ /**
2018
+ * Tone anchor only (spec §5: audience is tone-anchoring only, no rendering
2019
+ * effect on the IR yet). Reserved for a future lint pass (e.g. executive ×
2020
+ * long paragraphs → suggest kpi_cards/verdict_banner instead). The rule set
2021
+ * itself is explicitly out of scope this wave (spec §10 open questions).
2022
+ * Name unchanged by the vocabulary-v4 rename (spec §4.3: "audience 保持不变").
2023
+ */
2024
+ type Audience = (typeof AUDIENCE_VALUES)[number];
2025
+ interface NarrativeProfile {
2026
+ readonly strategy: Strategy;
2027
+ readonly pacing: Pacing;
2028
+ readonly audience: Audience;
2029
+ }
2030
+
2031
+ interface StrategyDefinition {
2032
+ id: Strategy;
2033
+ /**
2034
+ * Layout/component tendency set (soft-weight material). Filled row-for-row
2035
+ * from spec §5's strategy table for W4's weighted selection step (spec §6
2036
+ * step 4: in-set candidates get ×3 weight, out-of-set ×1 floor — not
2037
+ * implemented yet, this module only stores the data).
2038
+ *
2039
+ * The set deliberately mixes two different vocabularies: component `type`
2040
+ * names (e.g. "kpi_cards", "chart" — see the `Component` discriminated
2041
+ * union in `ir/index.ts`) and layout `id`s (e.g. "image-split" — see
2042
+ * `LAYOUT_REGISTRY` in `svg/layouts/registry.ts`). This is intentional,
2043
+ * not an oversight: W4's weighting step resolves each entry against
2044
+ * whichever vocabulary it belongs to (component types when scoring a
2045
+ * candidate's components, layout ids when scoring the candidate layout
2046
+ * itself). Nothing in this wave (W3) reads this field.
2047
+ */
2048
+ tendencies: readonly string[];
2049
+ /**
2050
+ * Content-archetype soft-weight set for W4's weighted layout selection
2051
+ * (spec §6 step 4: in-set candidates get `TENDENCY_WEIGHT` (×3), out-of-set
2052
+ * get the `BASE_WEIGHT` floor (×1) — both named constants live in
2053
+ * `svg/effective-layout.ts`, next to `resolveArchetypeId`, the sole
2054
+ * consumer). Deliberately a separate field from {@link tendencies} above,
2055
+ * not a reinterpretation of it: `tendencies` mixes component-type names and
2056
+ * layout ids drawn from spec §5's strategy table verbatim and also feeds W5's
2057
+ * spec `focus` vocabulary gate, so narrowing its meaning here would be a
2058
+ * breaking change to an existing consumer. This field holds only
2059
+ * `LAYOUT_REGISTRY` content-archetype ids (`svg/layouts/registry.ts`'s
2060
+ * `CONTENT_LAYOUTS` keys) — cover/chapter/ending ids never appear in any
2061
+ * strategy's list here, which is exactly why `resolveArchetypeId`'s weighting
2062
+ * is a no-op for those three slide types (spec: "身份页个性来自 theme 不来自
2063
+ * strategy,均匀取样") without needing a slide-type special case — a weight
2064
+ * lookup against an id that can never match falls through to the ×1 floor
2065
+ * for every candidate, uniform by construction. `tone-adaptive-content`
2066
+ * appears in no strategy's list either (spec's "万金油" call-out: it is the
2067
+ * one content archetype meant to read as strategy-neutral, so it always
2068
+ * gets the ×1 floor too).
2069
+ */
2070
+ layoutTendencies: readonly string[];
2071
+ /**
2072
+ * Beat template descriptor (spec §5's per-strategy beat-default column,
2073
+ * renamed from "rhythm" — spec §2.3), parameterized by strategy for W5's
2074
+ * spec-validate rotation gate — e.g. briefing is exempt from a generic
2075
+ * "three same-beat pages in a row is an error" rule because uniform-dense
2076
+ * *is* briefing's correct default, not a violation of it (spec §5's
2077
+ * spec-gate section calls out that a generic same-beat-streak rule would
2078
+ * reject briefing's own default). Not consumed this wave.
2079
+ */
2080
+ beatPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
2081
+ }
2082
+ declare const STRATEGY_DEFINITIONS: Record<Strategy, StrategyDefinition>;
2083
+
2084
+ interface PacingBudget {
2085
+ /**
2086
+ * Body-text baseline, in px, at 1280×720 slide geometry (spec §5 pacing
2087
+ * table's body-baseline column). Wired into rendering as of W4 task 3
2088
+ * (design decision 9): `src/svg/FullSlideSvg.tsx` resolves
2089
+ * `PACING_BUDGETS[resolveNarrative(ir.narrative).pacing].bodyBaselinePx`
2090
+ * once and passes it into `buildCtx`, which stores it as
2091
+ * `ComponentCtx.bodyFontPx` — the sole font-size input for the
2092
+ * paragraph/bullets/callout trio ("正文" = continuous running text).
2093
+ * Every other component's own bespoke type scale, the heading system, and
2094
+ * quote's fixed 26px attribution line don't read this field. Since
2095
+ * `balanced` (24px) is the narrative default, an omitted-narrative deck
2096
+ * now renders body text at 24px, not the previous fixed 20px — the
2097
+ * one-time, spec-sanctioned snapshot re-pin that landed alongside this
2098
+ * wiring (W4 task 3, unchanged since — vocabulary-v4 only renames this
2099
+ * table, it doesn't touch any of its numbers, spec §10).
2100
+ */
2101
+ bodyBaselinePx: number;
2102
+ /**
2103
+ * Per-slide editorial budget (component count) — content discipline
2104
+ * ("how many things belong on this slide"), not geometry. Spec §5's
2105
+ * dual-attribute capacity split keeps *physical* capacity ("how many
2106
+ * things fit in this layout's slots") on the layout registry's body-slot
2107
+ * `capacity` metadata (`svg/layouts/registry.ts`). The W3 quality gate
2108
+ * takes `min(this budget, the resolved layout's body capacity)`.
2109
+ */
2110
+ maxComponentsPerSlide: number;
2111
+ bullets: {
2112
+ maxItems: number;
2113
+ /**
2114
+ * Same "unit" concept the deleted `CAPACITY.bullets` used
2115
+ * (`measureTextUnits`, CJK weight = 1.0) — a visual-width-weighted
2116
+ * character count, not a raw `.length`. The old physical ceiling (53,
2117
+ * derived from render geometry) was deleted in W3 without a replacement
2118
+ * check because every pacing's editorial budget here (30/40/48) is
2119
+ * already strictly tighter than it — the quality gate now applies
2120
+ * exactly this one number per pacing.
2121
+ */
2122
+ maxUnitsPerItem: number;
2123
+ };
2124
+ }
2125
+ /**
2126
+ * Pinned to spec §5's pacing table (`bodyBaselinePx` / editorial budget /
2127
+ * bullets budget columns): dense 20/5/6×48, balanced 24/4/5×40, spacious
2128
+ * 32/3/4×30 — same three rows as the pre-rename `text`/`balanced`/
2129
+ * `presentation` delivery table, values byte-for-byte unchanged (spec §10).
2130
+ */
2131
+ declare const PACING_BUDGETS: Record<Pacing, PacingBudget>;
2132
+
2133
+ interface NarrativePreset {
2134
+ id: string;
2135
+ axes: NarrativeProfile;
2136
+ /**
2137
+ * Soft theme recommendations — a suggestion, never a hard constraint
2138
+ * (spec §5). Surfaced in workflow step ① so an agent can open with a
2139
+ * themed proposal — the user may still pick any theme. Every entry here
2140
+ * must be a real `BUILTIN_THEME_IDS` member (`ir/index.ts`) — enforced by
2141
+ * this module's test suite, which imports and tests against it.
2142
+ */
2143
+ themeRecommendations: readonly string[];
2144
+ }
2145
+ /**
2146
+ * Pinned to spec §5's 7 named presets and their theme recommendation table.
2147
+ * Preset ids are unchanged by the vocabulary-v4 rename (spec §5: "预设 ID
2148
+ * 保持不变") — only each preset's internal axes keys/values follow the
2149
+ * strategy/pacing rename (e.g. `annual-review`'s `mode: "narrative"` is now
2150
+ * `strategy: "storytelling"`, spec §5's own worked example).
2151
+ */
2152
+ declare const NARRATIVE_PRESETS: Record<string, NarrativePreset>;
2153
+ /**
2154
+ * = `NARRATIVE_PRESETS.general.axes` (briefing × balanced × public) — the
2155
+ * global default when narrative is omitted entirely (spec §5's defaults
2156
+ * chain).
2157
+ */
2158
+ declare const DEFAULT_NARRATIVE: NarrativeProfile;
2159
+ /**
2160
+ * Resolve a narrative input down to concrete axes, per spec §5's design
2161
+ * principle "omission gets the default, a typo is a hard error" (weak-model
2162
+ * friendly: a model that leaves a field out gets a sane deck, a model that
2163
+ * misspells a value gets a loud, actionable error instead of a silently
2164
+ * wrong deck):
2165
+ *
2166
+ * - `undefined` → {@link DEFAULT_NARRATIVE} (the `general` preset's axes)
2167
+ * - a preset id string → that preset's axes (unknown id throws
2168
+ * {@link PptfastError}, listing the available preset ids)
2169
+ * - a partial axes object → each axis defaults independently
2170
+ * (strategy → "briefing", pacing → "balanced", audience → "public" — these
2171
+ * happen to equal `DEFAULT_NARRATIVE`'s values because `general` *is* that
2172
+ * exact combination, but the fallback here is per-axis, not "any omitted
2173
+ * axis falls back to the whole default object")
2174
+ *
2175
+ * An unknown axis value, or an unknown key on the partial axes object,
2176
+ * always throws {@link PptfastError} (never silently ignored or dropped) —
2177
+ * omission and a typo are different intents, and only the former has a
2178
+ * reasonable default.
2179
+ *
2180
+ * Renamed from `resolveScenario` (spec §8.1). Callers that still hold a
2181
+ * pre-rename `mode`/`delivery` shaped input (e.g. a v3 IR's `scenario`
2182
+ * field) must migrate it first — see `migrateIrV3ToV4`
2183
+ * (`src/ir/migrate.ts`) for the deterministic field/value mapping. A
2184
+ * v4-track document that still writes the old field/value spelling gets no
2185
+ * such rescue (spec §16, reversing the now-superseded §15.4): this function
2186
+ * hard-errors on it, same as any other unknown axis key or value.
2187
+ */
2188
+ declare function resolveNarrative(input: string | Partial<NarrativeProfile> | undefined): NarrativeProfile;
2189
+
2190
+ /**
2191
+ * Deck spec schema + validation (spec §5 "plan artifact and hard gates", W5
2192
+ * task 2 — renamed to "Deck Spec" per the vocabulary-v4 rename, spec §6/§8.1;
2193
+ * "spec §N" citations throughout this file that predate this rename still
2194
+ * point at that original W5 design doc, not this rename's own spec — left
2195
+ * as historical citations, not renumbered).
2196
+ *
2197
+ * A deck spec is a workflow artifact, not a render prerequisite (spec §5's
2198
+ * "escape hatch": a bare IR v3 renders directly, `spec validate` is a
2199
+ * separate, optional gate for the spec-authoring stage of the six-phase
2200
+ * workflow). This module stays pure and Node-free — no `fs`, no CLI concerns
2201
+ * — so it can sit in `src/index.ts`'s dependency closure exactly like
2202
+ * `src/ir` and `src/narrative` already do (`AGENTS.md`'s layout rule).
2203
+ * `src/cli/commands.ts` owns the one Node-touching wrapper
2204
+ * (`runSpecValidate`: read file, call {@link validateSpec}, format the
2205
+ * result).
2206
+ *
2207
+ * Design mirrors `api.ts`'s `validateIr`/`ValidateResult` throughout
2208
+ * (structural zod pass first, then a sequential chain of hard-gate
2209
+ * categories, each short-circuiting the chain on its own failure — see
2210
+ * {@link validateSpec}'s own comment for why): same overall shape, adapted
2211
+ * for spec pages being keyed by an author-assigned `id` instead of IR's
2212
+ * positional slide index.
2213
+ */
2214
+
2215
+ /**
2216
+ * Mirrors `SlideSchema.type` / `SlideType` (`ir/index.ts`,
2217
+ * `svg/layouts/registry.ts`) exactly. Kept as an independent literal tuple
2218
+ * here (not imported from either) — a page spec's `type` is a 4-value enum
2219
+ * on its own schema, not a re-export of IR's — but the `satisfies` clause
2220
+ * makes any future drift between the two a compile error instead of a
2221
+ * silent mismatch.
2222
+ */
2223
+ declare const PAGE_TYPES: readonly ["cover", "chapter", "content", "ending"];
2224
+ declare const BEAT_VALUES: readonly ["anchor", "dense", "breathing"];
2225
+ type PageSpecType = (typeof PAGE_TYPES)[number];
2226
+ type PageBeat = (typeof BEAT_VALUES)[number];
2227
+ /**
2228
+ * A single page spec (spec §5, §6). `type`/`heading` are required — unlike
2229
+ * IR's own `SlideSchema` (where both default/omit for weak-model
2230
+ * friendliness), a deck spec is the authoring artifact those fields get
2231
+ * *locked* from at assemble time (W5 task 3), so leaving either implicit
2232
+ * here would defeat the point. `beat`/`focus`/`summary` stay optional per
2233
+ * spec §5's defaults chain ("beat omitted → auto-rotates by page position —
2234
+ * focus/summary/layout/slot can all be omitted").
2235
+ */
2236
+ declare const PageSpecSchema: z.ZodObject<{
2237
+ id: z.ZodString;
2238
+ type: z.ZodEnum<{
2239
+ cover: "cover";
2240
+ chapter: "chapter";
2241
+ content: "content";
2242
+ ending: "ending";
2243
+ }>;
2244
+ heading: z.ZodString;
2245
+ beat: z.ZodOptional<z.ZodEnum<{
2246
+ anchor: "anchor";
2247
+ dense: "dense";
2248
+ breathing: "breathing";
2249
+ }>>;
2250
+ focus: z.ZodOptional<z.ZodString>;
2251
+ summary: z.ZodOptional<z.ZodString>;
2252
+ }, z.core.$strict>;
2253
+ type PageSpec = z.infer<typeof PageSpecSchema>;
2254
+ /**
2255
+ * Top-level deck spec shape (spec §5, §6). `narrative`/`theme` deliberately
2256
+ * have no schema-level `.default(...)` — same reasoning as `PptxIRSchema`'s
2257
+ * own `narrative` field (`ir/index.ts`): the resolved value is never baked
2258
+ * back into the parsed shape, {@link validateSpec} (here) and, later,
2259
+ * assemble (W5 task 3) each resolve it themselves. `seed` is accepted but
2260
+ * entirely unexamined by this module — "not validateSpec's concern" (spec's
2261
+ * own wording): assemble generates and suggests writing one back on first
2262
+ * materialization.
2263
+ *
2264
+ * `version` stays the literal `"1"` (unchanged value) but now carries an
2265
+ * independent Deck Spec versioning scheme (spec §6: "`deck.spec.json` 使用
2266
+ * 独立的 spec 版本 1。它是新工件,不继承 `deck.plan.json` 的版本号语义") —
2267
+ * this "1" is the Deck Spec artifact's own first version, not a continuation
2268
+ * of the old deck-plan artifact's version counter, even though the digit is
2269
+ * the same.
2270
+ */
2271
+ declare const DeckSpecSchema: z.ZodObject<{
2272
+ version: z.ZodDefault<z.ZodLiteral<"1">>;
2273
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
2274
+ theme: z.ZodOptional<z.ZodString>;
2275
+ filename: z.ZodOptional<z.ZodString>;
2276
+ seed: z.ZodOptional<z.ZodNumber>;
2277
+ meta: z.ZodDefault<z.ZodObject<{
2278
+ organization: z.ZodOptional<z.ZodString>;
2279
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
2280
+ name: z.ZodString;
2281
+ role: z.ZodOptional<z.ZodString>;
2282
+ org: z.ZodOptional<z.ZodString>;
2283
+ }, z.core.$strict>>>;
2284
+ date: z.ZodOptional<z.ZodString>;
2285
+ version: z.ZodOptional<z.ZodString>;
2286
+ confidentiality: z.ZodOptional<z.ZodEnum<{
2287
+ public: "public";
2288
+ internal: "internal";
2289
+ confidential: "confidential";
2290
+ restricted: "restricted";
2291
+ }>>;
2292
+ contact: z.ZodOptional<z.ZodObject<{
2293
+ name: z.ZodOptional<z.ZodString>;
2294
+ email: z.ZodOptional<z.ZodString>;
2295
+ phone: z.ZodOptional<z.ZodString>;
2296
+ website: z.ZodOptional<z.ZodString>;
2297
+ }, z.core.$strict>>;
2298
+ copyright: z.ZodOptional<z.ZodString>;
2299
+ animation: z.ZodOptional<z.ZodObject<{
2300
+ transition: z.ZodOptional<z.ZodEnum<{
2301
+ fade: "fade";
2302
+ push: "push";
2303
+ wipe: "wipe";
2304
+ none: "none";
2305
+ }>>;
2306
+ elements: z.ZodOptional<z.ZodEnum<{
2307
+ none: "none";
2308
+ auto: "auto";
2309
+ }>>;
2310
+ }, z.core.$strict>>;
2311
+ }, z.core.$strict>>;
2312
+ brand: z.ZodOptional<z.ZodObject<{
2313
+ logo_asset_id: z.ZodOptional<z.ZodString>;
2314
+ position: z.ZodOptional<z.ZodEnum<{
2315
+ tl: "tl";
2316
+ tr: "tr";
2317
+ bl: "bl";
2318
+ br: "br";
2319
+ }>>;
2320
+ }, z.core.$strict>>;
2321
+ pages: z.ZodArray<z.ZodObject<{
2322
+ id: z.ZodString;
2323
+ type: z.ZodEnum<{
2324
+ cover: "cover";
2325
+ chapter: "chapter";
2326
+ content: "content";
2327
+ ending: "ending";
2328
+ }>;
2329
+ heading: z.ZodString;
2330
+ beat: z.ZodOptional<z.ZodEnum<{
2331
+ anchor: "anchor";
2332
+ dense: "dense";
2333
+ breathing: "breathing";
2334
+ }>>;
2335
+ focus: z.ZodOptional<z.ZodString>;
2336
+ summary: z.ZodOptional<z.ZodString>;
2337
+ }, z.core.$strict>>;
2338
+ }, z.core.$strict>;
2339
+ type DeckSpec = z.infer<typeof DeckSpecSchema>;
2340
+ /** JSON Schema for the deck spec — feed this to a model before it writes one (see `pptfast schema --spec`). */
2341
+ declare function specJsonSchema(): Record<string, unknown>;
2342
+ interface SpecValidationIssue {
2343
+ path: string;
2344
+ message: string;
2345
+ /** The offending page's `id`, when the issue is scoped to one specific
2346
+ * page and that page's `id` could be determined — absent for deck-level
2347
+ * issues (e.g. "pages non-empty") and for structural issues on a page
2348
+ * whose own `id` itself failed to parse. */
2349
+ pageId?: string;
2350
+ }
2351
+ interface SpecValidateResult {
2352
+ ok: boolean;
2353
+ spec?: DeckSpec;
2354
+ errors: SpecValidationIssue[];
2355
+ }
2356
+ declare function formatSpecIssues(errors: SpecValidationIssue[]): string;
2357
+ /**
2358
+ * Deck-spec-level theme default (spec §5's defaults chain: "theme omitted →
2359
+ * consulting") — the same default IR's own `theme.id` field carries
2360
+ * (`ThemeSchema` in `ir/index.ts`). Exported so a caller already holding a
2361
+ * validated {@link DeckSpec} (the CLI's OK-summary line) doesn't re-derive
2362
+ * the fallback itself.
2363
+ */
2364
+ declare function resolveSpecThemeId(spec: DeckSpec): string;
2365
+ /**
2366
+ * Deck-level page-count range per pacing (spec §5's pacing table,
2367
+ * initial values — "dense 8-30 / balanced 6-24 / spacious 4-16", not yet
2368
+ * tuned against real usage). Independent of `PACING_BUDGETS`
2369
+ * (`narrative/index.ts`, per-slide component-count/bullets editorial
2370
+ * budget) — this is a separate, deck-wide page-count concern the spec calls
2371
+ * out as its own hard gate ("page count vs. pacing recommended range").
2372
+ * Message wording renamed from "delivery" to "pacing" (vocabulary-v4
2373
+ * residual, routed from the task 1 review) — the axis itself was already
2374
+ * `Pacing` at the type level, this closes the last stale word in the
2375
+ * error text.
2376
+ */
2377
+ declare const SPEC_PAGE_COUNT_RANGE: Record<Pacing, {
2378
+ min: number;
2379
+ max: number;
2380
+ }>;
2381
+ /**
2382
+ * Validate raw JSON against the spec schema, then — once it parses — run the
2383
+ * spec §5 hard-gate chain. Mirrors `validateIr`'s (`api.ts`) overall shape: a
2384
+ * structural zod pass first, then a sequence of isolated hard-gate
2385
+ * categories, each short-circuiting the whole chain on its own failure
2386
+ * (rather than accumulating errors across categories) so a later category
2387
+ * never has to guess at what an earlier, already-broken one would have
2388
+ * meant — e.g. beat-rotation and page-count both need a resolved
2389
+ * narrative, so nothing past the narrative/theme stage runs until that
2390
+ * resolves cleanly. Every spec-gate philosophy here is "hard block, no soft
2391
+ * warning" (spec §5's "escape hatch" section — a spec that doesn't fit this shape
2392
+ * should be authored as bare IR instead, not warned-and-shipped).
2393
+ */
2394
+ declare function validateSpec(input: unknown): SpecValidateResult;
2395
+
2396
+ /**
2397
+ * One page's fillable content — everything a page spec's `id` does *not*
2398
+ * already lock in. Deliberately excludes `type`/`heading` (spec-owned, see
2399
+ * {@link assembleDeck}'s locked-field gate) and `subheading`/`decor`
2400
+ * (legitimate `Slide` fields, but outside this record's shape by spec §7's
2401
+ * own layout — "pages/<id>.json contains only components" — a spec/pages deck can't
2402
+ * author either one — a hand-authored bare IR still can). Every field here is
2403
+ * a same-name, same-shape subset of `Slide`'s own optional fields
2404
+ * (`../ir`'s `SlideSchema`) — reused, not redeclared, so the two can't drift.
2405
+ */
2406
+ interface PageContent {
2407
+ components?: Component[];
2408
+ layout?: string;
2409
+ arrangement?: NonNullable<Slide["arrangement"]>;
2410
+ background?: BackgroundSpec;
2411
+ image_side?: "left" | "right";
2412
+ footnote?: string;
2413
+ notes?: string;
2414
+ }
2415
+ interface AssembleResult {
2416
+ ir: PptxIR;
2417
+ /**
2418
+ * Set only when `spec.seed` was absent and {@link assembleDeck} generated
2419
+ * one deterministically (see the seed section of this function's doc
2420
+ * comment) — `undefined` when `spec.seed` was already present and simply
2421
+ * passed through. The CLI shell (W5 task 5) is the one that acts on this:
2422
+ * suggest writing the value back into the spec file, never rewrite it
2423
+ * itself (assemble stays a pure function, no fs side effects here).
2424
+ */
2425
+ generatedSeed?: number;
2426
+ /**
2427
+ * Count of pages whose `layout` field this call filled in via
2428
+ * {@link materializeEffectiveLayouts} (W4 design decision 10) —
2429
+ * `undefined` when zero, same "absent means nothing to report" posture as
2430
+ * {@link generatedSeed} just above (a page with an explicit pin already in
2431
+ * its page file, or every omitted page landing on the image-cover
2432
+ * takeover's `null` bypass, both leave this unset). Purely informational:
2433
+ * the CLI shell (`runAssemble`, `../cli/commands.ts`) surfaces it as a
2434
+ * one-line note, nothing here or downstream acts on the number itself.
2435
+ */
2436
+ materializedLayoutCount?: number;
2437
+ }
2438
+ /**
2439
+ * Assemble a validated deck spec plus a per-page content record into a
2440
+ * renderable IR. See this module's own top comment for the overall shape.
2441
+ * Step numbers below match the W5 task-3 brief's own numbered "inject
2442
+ * semantics" list verbatim — kept in that exact order because two of them
2443
+ * (locked-field vs. orphan) both throw and their relative order is
2444
+ * otherwise unobservable from either doc comment alone.
2445
+ *
2446
+ * 1. `spec` is `unknown` (same boundary `validateSpec` itself has — a spec
2447
+ * is almost always freshly `JSON.parse`d off disk by the caller) —
2448
+ * invalid shape or a failed hard gate throws {@link PptfastError} with
2449
+ * `validateSpec`'s own formatted issue list, not a re-derived message.
2450
+ * 2. Shape guard + locked-field protection: a `pages[id]` entry must first be
2451
+ * a plain object — not `null`, an array, or a primitive — else throws.
2452
+ * `pages` is `unknown`-shaped off disk same as `spec` itself (step 1), so
2453
+ * a JSON `null`/string/array content value is a real possibility, not
2454
+ * just a type-system hole, and `Object.hasOwn` throws its own
2455
+ * uninformative native `TypeError` on `null` (and silently no-ops on a
2456
+ * string/array/number) rather than this gate's own readable message.
2457
+ * Once that holds, a `pages[id]` entry that carries a `type` or `heading`
2458
+ * *key* — even set to `undefined` — throws. `Object.hasOwn`, not a
2459
+ * `!== undefined` read, is what makes that "even `undefined`" case
2460
+ * catchable: `PageContent` itself never declares either field, but a
2461
+ * page file freshly parsed off disk is `unknown` before it reaches this
2462
+ * function's declared `PageContent` parameter type, so a stray
2463
+ * `"heading": null`-turned-`undefined` or a copy-pasted empty key is
2464
+ * exactly the drift this gate exists to catch instead of silently
2465
+ * ignoring.
2466
+ * 3. Orphan keys: a `pages` entry whose id isn't any spec page's id. Listed
2467
+ * together with a fix suggestion, checked only after every present page
2468
+ * has cleared the locked-field gate (step 2) — an orphan file that
2469
+ * *also* happens to redeclare `heading` reports as locked-field first.
2470
+ * 4. Missing pages (a spec id with no `pages` entry) become a placeholder
2471
+ * slide — never an error. Spec §7's own words: "assemble's precise
2472
+ * semantics — a missing page always succeeds (placeholder), a structural
2473
+ * contradiction (orphan file / bad spec / id conflict) errors". A
2474
+ * declared `summary` becomes the placeholder's `subheading` (the one spot
2475
+ * `summary` — otherwise a spec-only anchor, see step 5 — does reach the
2476
+ * IR) so a `--draft` preview of an unfilled deck still reads as more than
2477
+ * a bare "Untitled".
2478
+ * 5. Present pages become a full slide: `id`/`type`/`heading` from the page
2479
+ * spec (never the content record — see step 2), plus whichever of
2480
+ * {@link PageContent}'s seven fields the content record actually set.
2481
+ * `beat`/`focus`/`summary` never reach the IR for a present page —
2482
+ * they are spec-only authoring anchors (beat/focus steer a future
2483
+ * fill/select step, summary is "for the fill step's own reading only"), not slide content.
2484
+ * 6. Top-level: `version` is always the literal `"4"` (IR's own version,
2485
+ * unrelated to the deck spec's own `version: "1"`) — the deck spec's own
2486
+ * `narrative` field (renamed this task from `scenario`, spec §8.1's
2487
+ * `DeckPlan`→`DeckSpec` rename, task 2) carries across into the v4 IR's
2488
+ * own `narrative` field, its value already in the new strategy/pacing
2489
+ * vocabulary (vocabulary-v4 rename, task 1 — `plan/index.ts`'s own
2490
+ * `resolveNarrative` call already validates it against that vocabulary
2491
+ * before this function ever runs).
2492
+ * `theme`/`filename`/`brand`/`meta`/`seed` (step 7) carry over from the spec when
2493
+ * present. When absent, this function omits the field from the raw
2494
+ * object it hands to {@link PptxIRSchema} rather than re-deriving IR's
2495
+ * own default value a second time — one default source of truth, the
2496
+ * schema itself (e.g. `theme` omitted here becomes `{ id: "consulting" }`,
2497
+ * exactly like a bare hand-authored IR that never mentions theme at all —
2498
+ * not a value this function needs to know).
2499
+ * 7. Seed: `spec.seed` present → passed through, `generatedSeed` stays
2500
+ * `undefined` on the result. Absent → {@link generateSeed} derives one
2501
+ * from `filename` + the spec's own ordered page-id list (never page
2502
+ * *content* — see that function's own doc comment for why), written to
2503
+ * `ir.seed` *and* returned as `generatedSeed` so a CLI shell can suggest
2504
+ * writing it back into the spec file (this function never touches disk
2505
+ * itself).
2506
+ * 8. Idempotence: every step above is a pure function of its inputs (no
2507
+ * randomness, no wall-clock, no reliance on unordered iteration) — two
2508
+ * calls with structurally-equal `spec`/`pages` produce deep-equal
2509
+ * results, `generatedSeed` included. Exercised directly by this module's
2510
+ * test suite rather than asserted here.
2511
+ */
2512
+ declare function assembleDeck(spec: unknown, pages: Record<string, PageContent>): AssembleResult;
2513
+ /**
2514
+ * Inverse of {@link assembleDeck}: reconstructs `{ spec, pages }` from an
2515
+ * existing IR well enough that `assembleDeck(...disassembleDeck(ir))`
2516
+ * reproduces every slide's content — but the map is not lossless in both
2517
+ * directions, only in the direction the round trip actually exercises
2518
+ * (IR → spec/pages → IR). Fields with no IR-side home never survive being
2519
+ * written *to* the IR in the first place, so there is nothing here to read
2520
+ * back:
2521
+ *
2522
+ * - `beat` and `focus` never appear on any produced {@link PageSpec} —
2523
+ * both are spec-only authoring anchors with no corresponding `Slide`
2524
+ * field at all (see {@link assembleDeck} step 5's doc comment). Nothing
2525
+ * here could recover a value that was never written anywhere.
2526
+ * - `summary` is recovered *only* for a placeholder slide, by reversing
2527
+ * step 4's `summary` → `subheading` injection (`slide.subheading` back to
2528
+ * `pageSpec.summary`). A non-placeholder slide's own `subheading` — a
2529
+ * legitimate, independent `Slide` field a hand-authored bare IR is free to
2530
+ * set — has no {@link PageContent} field to land in (spec §7's pages/
2531
+ * record is deliberately narrower than `Slide` itself, see that
2532
+ * interface's own doc comment) and no `summary` semantics either (`summary`
2533
+ * only ever flows to a *placeholder*'s `subheading`, never the reverse for
2534
+ * a filled page) — so it is dropped. Same for `decor`: a real `Slide`
2535
+ * field, absent from `PageContent`'s shape entirely.
2536
+ * - `theme.style` / `theme.brand` overrides collapse to a bare theme-id
2537
+ * string (`DeckSpecSchema.theme` has no shape for either) — only `theme.id`
2538
+ * survives. That `theme.brand` is `ThemeSchema.brand` (`BrandConfigSchema`
2539
+ * — `suppressFooterOnCardContent`/`suppressFooterRule`, footer-chrome
2540
+ * flags owned by the *theme*) — not to be confused with the deck-level
2541
+ * `brand` field below, a different, unrelated schema despite the shared
2542
+ * name.
2543
+ * - `ir.assets.images` is not part of this function's return value at all —
2544
+ * `{ spec, pages }` has no `assets` field, and this module stays zero-fs
2545
+ * by design (this file's own top comment), so it has no way to write an
2546
+ * `assets/` directory itself. Any `asset_id` reference inside a copied
2547
+ * `components`/`background` survives untouched ({@link extractPageContent}
2548
+ * copies both as-is), but the underlying image bytes are deliberately left
2549
+ * for the caller: the CLI shell closes that gap. `runDisassemble`
2550
+ * (`../cli/commands.ts`) walks the *input* IR's `ir.assets.images` itself
2551
+ * and materializes every entry into `<outDir>/assets/<id>.<ext>`
2552
+ * (`writeDeckAssets`, `../cli/deck-dir.ts`) so a later `readDeckDir`'s
2553
+ * `scanAssets` re-registers them exactly like any other deck-directory
2554
+ * asset. Skipping that shell-side step is not a doc-comment nuance — it
2555
+ * reproduces as a real bug: an image deck disassembles with every
2556
+ * `asset_id` left dangling, then re-assembles and renders with the image
2557
+ * silently missing.
2558
+ *
2559
+ * Round-trip-safe despite the above, worth calling out because of that name
2560
+ * collision: the top-level `brand` field (`BrandSchema` — `logo_asset_id` /
2561
+ * `position`, the deck logo/position `BrandChrome` reads,
2562
+ * `src/svg/BrandChrome.tsx`) is a plain passthrough on both sides
2563
+ * ({@link assembleDeck} step 6 reads `spec.brand` into `ir.brand` — this
2564
+ * function reads `ir.brand` back into `spec.brand` below) — carried through
2565
+ * unmodified, same as `narrative`/`filename`/`seed`, never
2566
+ * synthesized or dropped.
2567
+ *
2568
+ * `layout` deserves a different kind of callout: it round-trips as plain
2569
+ * content like any other field ({@link extractPageContent} copies
2570
+ * `slide.layout` into `PageContent.layout` whenever the slide has one, no
2571
+ * special case) — but a `deck.json` produced by {@link assembleDeck} now
2572
+ * carries a `layout` on nearly every slide (W4 design decision 10's
2573
+ * materialization, see that function's own doc comment), even on pages whose
2574
+ * *original page file* never set one. Nothing on `Slide` marks which is
2575
+ * which — `layout` is just a string either way — so disassembling an
2576
+ * already-assembled `deck.json` writes every materialized pick into the
2577
+ * regenerated page file as if it had been an explicit pin all along, and a
2578
+ * later re-`assembleDeck` call skips materialization for that page from then
2579
+ * on, same as any hand-authored pin. Accepted, not a bug: `disassembleDeck`
2580
+ * is a one-time bare-IR importer into an editable project directory, not a
2581
+ * round-trip channel for `deck.json` itself. A deck project's own
2582
+ * `pages/<id>.json` files are the durable, edit-in-place artifacts —
2583
+ * `deck.json` is downstream output, and feeding it back through
2584
+ * `disassembleDeck` (instead of editing the project directory that produced
2585
+ * it) is what actually costs those pages their revision-stability
2586
+ * re-selection eligibility going forward. A real, user-visible narrowing,
2587
+ * worth knowing about here, not worth adding code to guard against for a
2588
+ * usage pattern nothing in this codebase actually exercises.
2589
+ *
2590
+ * Two structural fields are synthesized rather than copied when the source
2591
+ * slide omits them, each documented where it is generated:
2592
+ * {@link UNTITLED_HEADING} for a missing/blank `heading`, and a positional
2593
+ * `p-<1-based-ordinal>-<type>` scheme for a missing `id` (stable across
2594
+ * repeated calls on the same IR — it is a pure function of slide position
2595
+ * and type — but, unlike a spec-assigned id, *not* stable across inserting
2596
+ * or reordering slides — out of scope here, since a bare IR with no `id` at
2597
+ * all has no stabler identity to fall back to in the first place).
2598
+ *
2599
+ * That generated `p-<ordinal>-<type>` id is safe by construction as a
2600
+ * page/asset file-name segment (W5 whole-branch review finding 1, verified
2601
+ * — not just asserted — rather than also routing it through
2602
+ * `assertSafeFileSegment`, `../cli/deck-dir.ts`): `<ordinal>` is
2603
+ * `index + 1`, always a plain non-negative integer, and `<type>` is a
2604
+ * `Slide["type"]`, a closed schema enum (`"cover" | "chapter" | "content" |
2605
+ * "ending"`, `SlideSchema.type` in `../ir/index.ts`) — neither half can ever
2606
+ * contain a `/`, a `\`, or resolve to `".."`, so the joined id can't either.
2607
+ * A carried-over `slide.id` (the `??` branch's other side) has no such
2608
+ * guarantee — that is the one this function passes straight through
2609
+ * unchecked, same as every other field {@link extractPageContent} copies —
2610
+ * which is exactly why the write-time gate in `../cli/deck-dir.ts` (not this
2611
+ * function) is what actually closes the vulnerability.
2612
+ */
2613
+ declare function disassembleDeck(ir: PptxIR): {
2614
+ spec: DeckSpec;
2615
+ pages: Record<string, PageContent>;
2616
+ };
2617
+
2618
+ /**
2619
+ * Deterministic, pure `deck.plan.json` → `deck.spec.json` migration (spec
2620
+ * §9.2, vocabulary-v4 rename, task 2). Mirrors `../ir/migrate.ts`'s
2621
+ * `migrateIrV3ToV4` in spirit — mechanical field rename only, no schema
2622
+ * validation, no model call, no content rewrite (same posture spec §9.3
2623
+ * states for the IR v3→v4 primitive: "只做已声明的结构映射,不运行模型,
2624
+ * 不重写内容,不重新选择 layout" — this function is that same contract
2625
+ * applied to the other artifact this rename touches). A caller should run
2626
+ * `validateSpec` (`./index.ts`) against the result to confirm it lands as a
2627
+ * legal deck spec, exactly as it would for a `deck.spec.json` authored by
2628
+ * hand — this function itself never parses against `DeckSpecSchema`.
2629
+ *
2630
+ * Field-for-field, value-for-value per spec §9.2's table:
2631
+ *
2632
+ * ```text
2633
+ * deck.plan.json → deck.spec.json
2634
+ * scenario → narrative
2635
+ * pages[].rhythm → pages[].beat
2636
+ * 其余字段 → 原样保留
2637
+ * ```
2638
+ *
2639
+ * Takes the raw, `JSON.parse`d `deck.plan.json` contents (`unknown`) rather
2640
+ * than an already-validated shape — unlike `migrateIrV3ToV4` (which takes an
2641
+ * already-`PptxIRV3Schema.parse`d object), there is no schema left in this
2642
+ * codebase a pre-rename plan file could validate against: `DeckSpecSchema`
2643
+ * (`./index.ts`) already requires the *post*-rename field names (`narrative`,
2644
+ * `beat`), so parsing a plan-shaped document against it would just fail on
2645
+ * the very keys this function exists to rename. Non-object input, or a
2646
+ * `pages` value that isn't an array, passes through completely unchanged —
2647
+ * reporting a malformed source file is `runMigrate`'s job
2648
+ * (`../cli/commands.ts`), not this mechanical rename step's.
2649
+ */
2650
+ declare function migrateDeckPlanToSpec(raw: unknown): unknown;
2651
+
2652
+ /**
2653
+ * `pptfast audit` finding shape (v0.3 W6, spec §7 workflow ④). `page` is
2654
+ * 1-based (matches `ValidationIssue.page` in `api.ts`); `slideId` is set
2655
+ * whenever the offending slide has a stable `id` (assemble-stamped or
2656
+ * author-supplied), same "page + optional id" convention `formatIssues`
2657
+ * already uses for validate errors.
2658
+ */
2659
+ interface AuditFinding {
2660
+ page: number;
2661
+ slideId?: string;
2662
+ code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap" | "content-truncated" | "content-dropped";
2663
+ message: string;
2664
+ detail?: Record<string, unknown>;
2665
+ }
2666
+ /**
2667
+ * Which check families actually ran (audit-v2 phase B, spec §4.2/§11.6) —
2668
+ * the one piece of the spec's original metrics design that survives into
2669
+ * this wave (§11.6: metrics itself deferred, "唯一现值是 checks 结构"). This
2670
+ * is the wave's own spirit made literal: a field can only ever be
2671
+ * `"completed"` once its check family genuinely ran over every audited
2672
+ * page, never a default that silently reads as "passed". `svg` has no
2673
+ * `"not-requested"` state — the deterministic SVG audit always runs, so it
2674
+ * is always `"completed"`. `pixels` starts `"not-requested"` and only ever
2675
+ * becomes `"completed"`; there is deliberately no `"failed"` value — a
2676
+ * failed pixel audit throws (spec §11.7's "契约层"), it never reports
2677
+ * itself as a completed check that happens to carry no findings.
2678
+ */
2679
+ interface AuditChecks {
2680
+ svg: "completed";
2681
+ pixels: "not-requested" | "completed";
2682
+ }
2683
+ interface AuditReport {
2684
+ findings: AuditFinding[];
2685
+ pagesAudited: number;
2686
+ pagesSkipped: number;
2687
+ checks: AuditChecks;
2688
+ }
2689
+ /** `auditDeck`'s second parameter (audit-v2 phase B) — see that function's
2690
+ * own doc comment for the overload contract this shape backs. */
2691
+ interface AuditDeckOptions {
2692
+ pixels?: boolean;
2693
+ }
2694
+ /**
2695
+ * The SDK entry point. Advisory, not a hard gate: `validateIr` already
2696
+ * rejects structurally invalid or over-dense decks before a caller ever
2697
+ * gets this far; this function looks for the visual problems that can still
2698
+ * slip through a valid deck at render time (an author-chosen near-background
2699
+ * text color, two components whose combined content happens to collide, a
2700
+ * card list that had to drop an item to fit). A non-empty `findings` array
2701
+ * is a prompt for a human/agent to look, not a rejection.
2702
+ *
2703
+ * Placeholder pages (`slide.placeholder === true`) are skipped — assemble's
2704
+ * stand-in for content nobody has written yet has nothing to audit, same
2705
+ * reasoning `checkIrQuality` already uses to skip them (`ir-quality.ts`).
2706
+ *
2707
+ * `auditDeck` itself never calls `installNodePlatform()`; that's the
2708
+ * caller's job (the CLI does it automatically).
2709
+ *
2710
+ * `opts.pixels` (audit-v2 phase B, spec §4.3/§11.7) opts into the optional
2711
+ * pixel-level contrast audit over image-backed text (`../svg/audit/pixel-audit.ts`)
2712
+ * — overloaded so the far more common omitted/`false` case keeps returning
2713
+ * a plain, synchronous `AuditReport` (spec §11.7's "语义层": the default
2714
+ * audit stays pure TS with zero added latency or Promise-wrapping), while
2715
+ * `pixels: true` returns a `Promise<AuditReport>` that resolves once the
2716
+ * rasterization pass completes. A caller holding a non-literal
2717
+ * `{ pixels: someBoolean }` won't match either overload directly (TypeScript
2718
+ * can't narrow it to one specific branch) — branch on the boolean first and
2719
+ * call this function with a literal in each arm, e.g. `opts.pixels ? await
2720
+ * auditDeck(ir, { pixels: true }) : auditDeck(ir)` (`cli/commands.ts`'s
2721
+ * `runAudit` does exactly this).
2722
+ */
2723
+ declare function auditDeck(ir: PptxIR, opts?: {
2724
+ pixels?: false;
2725
+ }): AuditReport;
2726
+ declare function auditDeck(ir: PptxIR, opts: {
2727
+ pixels: true;
2728
+ }): Promise<AuditReport>;
1075
2729
 
1076
- export { type Assets, type BackgroundSpec, type Block, type Meta, PptfastError, type PptxIR, PptxIRSchema, type Slide, THEME_IDS, type ThemeInfo, VERSION, type ValidateResult, type ValidationIssue, formatIssues, generatePptx, irJsonSchema, listThemes, renderSlideSvg, validateIr };
2730
+ export { AUDIENCE_VALUES, type AssembleResult, type Assets, type Audience, type AuditChecks, type AuditDeckOptions, type AuditFinding, type AuditReport, BUILTIN_THEME_IDS, type BackgroundSpec, type BrandConfig, BrandConfigSchema, COMPONENT_TYPES, type Component, DEFAULT_NARRATIVE, type DeckSpec, DeckSpecSchema, 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, PptfastError, 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, disassembleDeck, formatIssues, formatSpecIssues, generatePptx, getInstalledThemeIds, getThemeDefinition, irJsonSchema, listThemes, migrateDeckPlanToSpec, migrateIrV3ToV4, registerTheme, renderSlideSvg, resolveNarrative, resolveSpecThemeId, specJsonSchema, styleJsonSchema, validateIr, validateSpec };