@liustack/pptfast 0.3.0 → 0.6.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,7 +1,7 @@
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.3.0";
4
+ declare const VERSION = "0.6.0";
5
5
 
6
6
  /** Error class thrown by pptfast's public API surface (validation, rendering, export). */
7
7
  declare class PptfastError extends Error {
@@ -392,9 +392,51 @@ declare const ComponentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
392
392
  vs: "vs";
393
393
  before_after: "before_after";
394
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>>;
395
437
  }, z.core.$strict>], "type">;
396
438
  /**
397
- * All 24 component `type` discriminant values, derived from `ComponentSchema`
439
+ * All 28 component `type` discriminant values, derived from `ComponentSchema`
398
440
  * itself (never hand-copied) so this list can't drift from the union above.
399
441
  * Typed as plain `readonly string[]` rather than `Component["type"][]` —
400
442
  * every consumer of this list (W5's plan `focus` vocabulary gate,
@@ -671,6 +713,48 @@ declare const SlideSchema: z.ZodObject<{
671
713
  vs: "vs";
672
714
  before_after: "before_after";
673
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>>;
674
758
  }, z.core.$strict>], "type">>>;
675
759
  background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
676
760
  kind: z.ZodLiteral<"color">;
@@ -715,11 +799,12 @@ declare const SlideSchema: z.ZodObject<{
715
799
  right: "right";
716
800
  }>>;
717
801
  footnote: z.ZodOptional<z.ZodString>;
802
+ notes: z.ZodOptional<z.ZodString>;
718
803
  }, z.core.$strict>;
719
804
  declare const PptxIRSchema: z.ZodObject<{
720
- version: z.ZodDefault<z.ZodLiteral<"3">>;
805
+ version: z.ZodDefault<z.ZodLiteral<"4">>;
721
806
  filename: z.ZodDefault<z.ZodString>;
722
- scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
807
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
723
808
  theme: z.ZodDefault<z.ZodObject<{
724
809
  id: z.ZodDefault<z.ZodString>;
725
810
  style: z.ZodOptional<z.ZodObject<{
@@ -1071,6 +1156,48 @@ declare const PptxIRSchema: z.ZodObject<{
1071
1156
  vs: "vs";
1072
1157
  before_after: "before_after";
1073
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>>;
1074
1201
  }, z.core.$strict>], "type">>>;
1075
1202
  background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1076
1203
  kind: z.ZodLiteral<"color">;
@@ -1115,6 +1242,7 @@ declare const PptxIRSchema: z.ZodObject<{
1115
1242
  right: "right";
1116
1243
  }>>;
1117
1244
  footnote: z.ZodOptional<z.ZodString>;
1245
+ notes: z.ZodOptional<z.ZodString>;
1118
1246
  }, z.core.$strict>>;
1119
1247
  }, z.core.$strict>;
1120
1248
  type PptxIR = z.infer<typeof PptxIRSchema>;
@@ -1134,8 +1262,9 @@ interface ValidationIssue {
1134
1262
  * whole-branch review finding 2: the README already claimed "validation
1135
1263
  * error messages reference [a slide] by [its] id"; this is what makes
1136
1264
  * that true. Set by every page-scoped issue producer
1137
- * ({@link checkLayoutApplicability}, the content-quality-gate
1138
- * translation in {@link validateIr}, {@link checkDuplicateSlideIds})
1265
+ * ({@link checkLayoutApplicability}, {@link checkBoundaryPageContent},
1266
+ * the content-quality-gate translation in {@link validateIr},
1267
+ * {@link checkDuplicateSlideIds})
1139
1268
  * when the slide in question has an `id` — absent when the slide has
1140
1269
  * none (bare, pre-W5 IR) or the issue is deck-level, not scoped to any
1141
1270
  * single slide. {@link formatIssues} appends it in parens after the page
@@ -1155,34 +1284,86 @@ interface ValidateResult {
1155
1284
  * least one rewrite happened; informational, never gates `ok` on its own.
1156
1285
  */
1157
1286
  normalized?: string[];
1287
+ /**
1288
+ * Backward-compatible addition (borrow wave, Task 2 — dual-threshold
1289
+ * severity recalibration): warn-severity {@link checkIrQuality} findings
1290
+ * (editorial-budget codes — `missing_heading`/`long_heading`/`density`/
1291
+ * `bullets_overflow`/`bullet_item_long`/`big_number_no_kpi`), formatted the
1292
+ * same shape {@link formatIssues} already prints for `errors` (page/id/path
1293
+ * all included). Present only when at least one warn-severity finding
1294
+ * exists, and never gates `ok` on its own — that is exactly what moved
1295
+ * these findings off `errors` and onto here. Can be present alongside a
1296
+ * failing (`ok:false`) result too, whenever `checkIrQuality` itself ran
1297
+ * (an *earlier* hard gate — schema, theme id, layout applicability,
1298
+ * full-body exclusivity, boundary-page content, duplicate ids, narrative —
1299
+ * short-circuits before `checkIrQuality` runs at all, so a deck rejected
1300
+ * by one of those never reaches this field either way, see `validateIr`'s
1301
+ * own body for the exact gate order). {@link formatWarnings} renders this
1302
+ * array as CLI-ready `"warning: ..."` lines.
1303
+ */
1304
+ warnings?: ValidationIssue[];
1158
1305
  }
1159
1306
  /**
1160
1307
  * Validate raw JSON against the IR schema, then — once it parses — resolve
1161
- * `scenario` (`resolveScenario`, spec §5: an unrecognized preset name is a
1162
- * `scenario`-path error, page-less) and run the content-quality gate
1163
- * (`checkIrQuality`, passed the resolved axes) against the parsed IR. All
1164
- * stages must pass for `ok: true`. Quality findings are reported the same
1165
- * way as schema errors (page-scoped, 1-based). `checkIrQuality` itself tags
1166
- * findings "warn" vs "error", but that split was designed for its original
1167
- * consumer (surfacing informational warnings in a UI after generation, per
1168
- * its own docstring) — here it is the pre-generation hard gate, so any
1169
- * finding blocks (`ok: false`), not only "error"-severity ones. Treating
1170
- * "warn" findings (e.g. a cover with no heading) as advisory-only would
1171
- * defeat the point of wiring this in: the spec's core principle is a hard
1172
- * protocol gate, not hoped-for prompt compliance.
1308
+ * `narrative` (`resolveNarrative`, spec §5: an unrecognized preset name is a
1309
+ * `narrative`-path error, page-less) and run the content-quality gate
1310
+ * (`checkIrQuality`, passed the resolved axes) against the parsed IR. Every
1311
+ * hard-gate stage (schema, theme id, layout applicability, full-body
1312
+ * exclusivity, boundary-page content, duplicate ids, narrative) must pass for
1313
+ * `ok: true`, same as before. Quality findings are reported the same way as
1314
+ * schema errors (page-scoped, 1-based).
1173
1315
  *
1174
- * Before any of that, `normalizeComponentAliases` (W5 task 4,
1175
- * `ir/field-aliases.ts`) gets one deterministic pass at `input` rewriting
1176
- * a known synonym field name (kpi `title`→`label`, quote `content`→`text`,
1177
- * …) only where the canonical key is absent, so the schema parse below never
1178
- * sees the alias as an "unrecognized key" in the first place. Ordering
1179
- * versus the v2 migration check right above it is unobservable (v2 IR uses
1180
- * `blocks`, never `components`, so the normalizer's slide/component walk is
1181
- * always a no-op on it) — it runs second here only so a doomed v2 input
1182
- * skips the extra walk. Purely informational: every rewrite is recorded as a
1183
- * human-readable `path: alias canonical` string and threaded onto
1316
+ * Dual-threshold severity (borrow wave, Task 2 recalibrated from the prior
1317
+ * "any finding blocks" design): `checkIrQuality`'s own "warn" vs "error" tag
1318
+ * is now respected, not flattened. Only "error"-severity findings
1319
+ * (`empty_deck`, `bullet_item_overflow` content genuinely lost: an empty
1320
+ * deck, or a bullet item long enough to hit `bullets.tsx`'s MIN_FONT floor
1321
+ * and actually get truncated) gate `ok`. "warn"-severity findings the
1322
+ * editorial-budget codes (`missing_heading`/`long_heading`/`density`/
1323
+ * `bullets_overflow`/`bullet_item_long`/`big_number_no_kpi`) — surface on
1324
+ * {@link ValidateResult.warnings} instead: visible to every caller (the SDK,
1325
+ * the CLI's `validate`/`render` pre-flight), never blocking. This reverses
1326
+ * the prior posture (this comment used to read "any finding blocks... not
1327
+ * only 'error'-severity ones") because the evidence behind it did not hold:
1328
+ * a boundary scan (borrow-wave fact-report, Q3) found the editorial bullets
1329
+ * threshold blocking content roughly 3.5x below where `bullets.tsx`'s own
1330
+ * render safety net (2-line wrap, shrink to a 14px floor) actually starts
1331
+ * losing characters — so the hard gate was rejecting deck content that
1332
+ * would have rendered with zero visible defect, and a tight hard gate on an
1333
+ * editorial (not geometric) threshold taught truncate-content-to-pass
1334
+ * workarounds rather than catching real loss. `generatePptx`'s default path
1335
+ * inherits this unchanged (`if (!v.ok) throw`) — it already only ever
1336
+ * blocked on `ok`, never inspected `errors`/`warnings` directly.
1337
+ *
1338
+ * Before any of that, one deterministic alias pass runs
1339
+ * ({@link normalizeComponentAliases}, W5 task 4) for the component
1340
+ * field-name synonym rescue only (kpi `title`→`label`, quote
1341
+ * `content`→`text`, …) — a weak-model rescue for schema-internal synonym
1342
+ * drift, scoped to `slides[]`. It only rewrites where the canonical key is
1343
+ * absent, so the schema parse below never sees an alias as an "unrecognized
1344
+ * key" in the first place. Purely informational: every rewrite is recorded
1345
+ * as a human-readable `path: alias → canonical` string and threaded onto
1184
1346
  * `ValidateResult.normalized` on *every* return path below via
1185
1347
  * `withNormalized`, success or failure alike — it never itself gates `ok`.
1348
+ *
1349
+ * There is deliberately no root/narrative-level alias pass (spec §16,
1350
+ * reversing the now-superseded §15.4): a v4 document that still spells its
1351
+ * pre-rename vocabulary — `scenario` instead of `narrative`, `mode`/
1352
+ * `delivery` instead of `strategy`/`pacing`, or the old enum values
1353
+ * `"text"`/`"presentation"`/`"narrative"` — is not old-vocabulary
1354
+ * *compatibility*, it is exactly the vocabulary this rename retired, so it
1355
+ * hard-errors like any other unknown key or value: `scenario` fails the
1356
+ * schema's `.strict()` parse below as an unrecognized key, and an old enum
1357
+ * value (or the axis-key names `mode`/`delivery` inside `narrative`, which
1358
+ * the schema itself leaves open) fails `resolveNarrative`'s own runtime
1359
+ * check, listing the current values. `pptfast migrate` (`ir/migrate.ts`)
1360
+ * remains the sanctioned bridge for a genuine v3 document — see the v3 hard
1361
+ * reject below, which points there.
1362
+ *
1363
+ * The component-alias pass only ever runs for a document already headed for
1364
+ * the v4 schema — an explicit `version: "2"` or `version: "3"` is
1365
+ * hard-rejected first, below, before the alias pass or any schema parse
1366
+ * (spec §9.3: a v2/v3 document is never silently reinterpreted as v4).
1186
1367
  */
1187
1368
  declare function validateIr(input: unknown): ValidateResult;
1188
1369
  /**
@@ -1215,6 +1396,494 @@ declare function irJsonSchema(): Record<string, unknown>;
1215
1396
  /** JSON Schema for style-token overrides (IR theme.style, --style files, config "style"). */
1216
1397
  declare function styleJsonSchema(): Record<string, unknown>;
1217
1398
 
1399
+ /**
1400
+ * The frozen IR v3 top-level shape (vocabulary-v4 rename, task 1 — spec
1401
+ * §9.3: "v3 已冻结... 顶层字段和枚举改名必须进入新的 IR 版本,不能在 v3
1402
+ * 内静默改变含义"). `./index.ts`'s `PptxIRSchema` is v4 now — this module
1403
+ * exists only so a genuinely v3-shaped document still has somewhere to parse
1404
+ * against: `migrateIrV3ToV4`'s input type (`./migrate.ts`), and the
1405
+ * v3-hard-reject path's own tests (constructing a *valid* v3 IR to prove the
1406
+ * reject fires on version alone, not on some other schema defect).
1407
+ *
1408
+ * `validateIr` (`src/api.ts`) never calls this schema itself — an incoming
1409
+ * v3 document (`version === "3"`) is hard-rejected before any schema parse
1410
+ * runs at all (spec §9.3), full stop. This schema is a migration-tooling and
1411
+ * test fixture, not a second accepted input shape.
1412
+ *
1413
+ * Every field but `version` and `scenario` is byte-identical to `./index.ts`'s
1414
+ * v4 `PptxIRSchema` (spec §9.1: "其余 IR 字段保持不变") — reuses the exact
1415
+ * same `ThemeSchema`/`MetaSchema`/`AssetsSchema`/`BrandSchema`/`SlideSchema`
1416
+ * instances rather than redefining them, so there is no way for this frozen
1417
+ * shape to silently drift from the fields it shares with v4.
1418
+ */
1419
+ declare const PptxIRV3Schema: z.ZodObject<{
1420
+ version: z.ZodDefault<z.ZodLiteral<"3">>;
1421
+ filename: z.ZodDefault<z.ZodString>;
1422
+ scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1423
+ theme: z.ZodDefault<z.ZodObject<{
1424
+ id: z.ZodDefault<z.ZodString>;
1425
+ style: z.ZodOptional<z.ZodObject<{
1426
+ colors: z.ZodOptional<z.ZodObject<{
1427
+ bg: z.ZodOptional<z.ZodString>;
1428
+ surface: z.ZodOptional<z.ZodString>;
1429
+ panel: z.ZodOptional<z.ZodString>;
1430
+ primary: z.ZodOptional<z.ZodString>;
1431
+ accent: z.ZodOptional<z.ZodString>;
1432
+ text: z.ZodOptional<z.ZodString>;
1433
+ muted: z.ZodOptional<z.ZodString>;
1434
+ border: z.ZodOptional<z.ZodString>;
1435
+ chartPalette: z.ZodOptional<z.ZodArray<z.ZodString>>;
1436
+ accentPool: z.ZodOptional<z.ZodArray<z.ZodString>>;
1437
+ cardStroke: z.ZodOptional<z.ZodString>;
1438
+ }, z.core.$strict>>;
1439
+ fonts: z.ZodOptional<z.ZodObject<{
1440
+ heading: z.ZodOptional<z.ZodArray<z.ZodString>>;
1441
+ body: z.ZodOptional<z.ZodArray<z.ZodString>>;
1442
+ mono: z.ZodOptional<z.ZodArray<z.ZodString>>;
1443
+ }, z.core.$strict>>;
1444
+ shape: z.ZodOptional<z.ZodObject<{
1445
+ radius: z.ZodOptional<z.ZodNumber>;
1446
+ gapScale: z.ZodOptional<z.ZodNumber>;
1447
+ }, z.core.$strict>>;
1448
+ }, z.core.$strict>>;
1449
+ brand: z.ZodOptional<z.ZodObject<{
1450
+ suppressFooterOnCardContent: z.ZodOptional<z.ZodBoolean>;
1451
+ suppressFooterRule: z.ZodOptional<z.ZodBoolean>;
1452
+ }, z.core.$strict>>;
1453
+ }, z.core.$strict>>;
1454
+ meta: z.ZodDefault<z.ZodObject<{
1455
+ organization: z.ZodOptional<z.ZodString>;
1456
+ authors: z.ZodOptional<z.ZodArray<z.ZodObject<{
1457
+ name: z.ZodString;
1458
+ role: z.ZodOptional<z.ZodString>;
1459
+ org: z.ZodOptional<z.ZodString>;
1460
+ }, z.core.$strict>>>;
1461
+ date: z.ZodOptional<z.ZodString>;
1462
+ version: z.ZodOptional<z.ZodString>;
1463
+ confidentiality: z.ZodOptional<z.ZodEnum<{
1464
+ public: "public";
1465
+ internal: "internal";
1466
+ confidential: "confidential";
1467
+ restricted: "restricted";
1468
+ }>>;
1469
+ contact: z.ZodOptional<z.ZodObject<{
1470
+ name: z.ZodOptional<z.ZodString>;
1471
+ email: z.ZodOptional<z.ZodString>;
1472
+ phone: z.ZodOptional<z.ZodString>;
1473
+ website: z.ZodOptional<z.ZodString>;
1474
+ }, z.core.$strict>>;
1475
+ copyright: z.ZodOptional<z.ZodString>;
1476
+ animation: z.ZodOptional<z.ZodObject<{
1477
+ transition: z.ZodOptional<z.ZodEnum<{
1478
+ fade: "fade";
1479
+ push: "push";
1480
+ wipe: "wipe";
1481
+ none: "none";
1482
+ }>>;
1483
+ elements: z.ZodOptional<z.ZodEnum<{
1484
+ none: "none";
1485
+ auto: "auto";
1486
+ }>>;
1487
+ }, z.core.$strict>>;
1488
+ }, z.core.$strict>>;
1489
+ assets: z.ZodDefault<z.ZodObject<{
1490
+ images: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1491
+ src: z.ZodString;
1492
+ alt: z.ZodOptional<z.ZodString>;
1493
+ error: z.ZodOptional<z.ZodString>;
1494
+ }, z.core.$strict>>>;
1495
+ }, z.core.$strict>>;
1496
+ brand: z.ZodOptional<z.ZodObject<{
1497
+ logo_asset_id: z.ZodOptional<z.ZodString>;
1498
+ position: z.ZodOptional<z.ZodEnum<{
1499
+ tl: "tl";
1500
+ tr: "tr";
1501
+ bl: "bl";
1502
+ br: "br";
1503
+ }>>;
1504
+ }, z.core.$strict>>;
1505
+ seed: z.ZodOptional<z.ZodNumber>;
1506
+ slides: z.ZodArray<z.ZodObject<{
1507
+ type: z.ZodDefault<z.ZodEnum<{
1508
+ cover: "cover";
1509
+ chapter: "chapter";
1510
+ content: "content";
1511
+ ending: "ending";
1512
+ }>>;
1513
+ id: z.ZodOptional<z.ZodString>;
1514
+ placeholder: z.ZodOptional<z.ZodLiteral<true>>;
1515
+ layout: z.ZodOptional<z.ZodString>;
1516
+ arrangement: z.ZodOptional<z.ZodEnum<{
1517
+ code: "code";
1518
+ quote: "quote";
1519
+ single: "single";
1520
+ two_column: "two_column";
1521
+ kpi_focus: "kpi_focus";
1522
+ image_focus: "image_focus";
1523
+ big_number: "big_number";
1524
+ assertion_evidence: "assertion_evidence";
1525
+ aside: "aside";
1526
+ }>>;
1527
+ heading: z.ZodOptional<z.ZodString>;
1528
+ subheading: z.ZodOptional<z.ZodString>;
1529
+ components: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1530
+ type: z.ZodLiteral<"bullets">;
1531
+ items: z.ZodArray<z.ZodString>;
1532
+ style: z.ZodOptional<z.ZodEnum<{
1533
+ default: "default";
1534
+ checklist: "checklist";
1535
+ numbered: "numbered";
1536
+ plain: "plain";
1537
+ divided: "divided";
1538
+ }>>;
1539
+ }, z.core.$strict>, z.ZodObject<{
1540
+ type: z.ZodLiteral<"paragraph">;
1541
+ text: z.ZodString;
1542
+ }, z.core.$strict>, z.ZodObject<{
1543
+ type: z.ZodLiteral<"quote">;
1544
+ text: z.ZodString;
1545
+ attribution: z.ZodOptional<z.ZodString>;
1546
+ }, z.core.$strict>, z.ZodObject<{
1547
+ type: z.ZodLiteral<"callout">;
1548
+ variant: z.ZodEnum<{
1549
+ info: "info";
1550
+ warn: "warn";
1551
+ tip: "tip";
1552
+ }>;
1553
+ text: z.ZodString;
1554
+ icon: z.ZodOptional<z.ZodEnum<{
1555
+ [x: string]: string;
1556
+ }>>;
1557
+ }, z.core.$strict>, z.ZodObject<{
1558
+ type: z.ZodLiteral<"code">;
1559
+ language: z.ZodString;
1560
+ code: z.ZodString;
1561
+ }, z.core.$strict>, z.ZodObject<{
1562
+ type: z.ZodLiteral<"kpi_cards">;
1563
+ items: z.ZodArray<z.ZodObject<{
1564
+ value: z.ZodString;
1565
+ unit: z.ZodOptional<z.ZodString>;
1566
+ label: z.ZodString;
1567
+ delta: z.ZodOptional<z.ZodEnum<{
1568
+ up: "up";
1569
+ down: "down";
1570
+ flat: "flat";
1571
+ }>>;
1572
+ icon: z.ZodOptional<z.ZodEnum<{
1573
+ [x: string]: string;
1574
+ }>>;
1575
+ source: z.ZodOptional<z.ZodString>;
1576
+ }, z.core.$strict>>;
1577
+ }, z.core.$strict>, z.ZodObject<{
1578
+ type: z.ZodLiteral<"chart">;
1579
+ chart_type: z.ZodEnum<{
1580
+ line: "line";
1581
+ dumbbell: "dumbbell";
1582
+ funnel: "funnel";
1583
+ bar: "bar";
1584
+ pie: "pie";
1585
+ }>;
1586
+ direction: z.ZodOptional<z.ZodEnum<{
1587
+ horizontal: "horizontal";
1588
+ vertical: "vertical";
1589
+ }>>;
1590
+ style: z.ZodOptional<z.ZodEnum<{
1591
+ donut: "donut";
1592
+ }>>;
1593
+ axes: z.ZodOptional<z.ZodObject<{
1594
+ x_title: z.ZodOptional<z.ZodString>;
1595
+ y_title: z.ZodOptional<z.ZodString>;
1596
+ show_grid: z.ZodOptional<z.ZodBoolean>;
1597
+ }, z.core.$strict>>;
1598
+ series: z.ZodArray<z.ZodObject<{
1599
+ name: z.ZodString;
1600
+ data: z.ZodArray<z.ZodObject<{
1601
+ x: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1602
+ y: z.ZodNumber;
1603
+ }, z.core.$strict>>;
1604
+ }, z.core.$strict>>;
1605
+ }, z.core.$strict>, z.ZodObject<{
1606
+ type: z.ZodLiteral<"flowchart">;
1607
+ nodes: z.ZodArray<z.ZodObject<{
1608
+ id: z.ZodString;
1609
+ label: z.ZodString;
1610
+ kind: z.ZodOptional<z.ZodEnum<{
1611
+ rect: "rect";
1612
+ diamond: "diamond";
1613
+ round: "round";
1614
+ }>>;
1615
+ }, z.core.$strict>>;
1616
+ edges: z.ZodArray<z.ZodObject<{
1617
+ from: z.ZodString;
1618
+ to: z.ZodString;
1619
+ label: z.ZodOptional<z.ZodString>;
1620
+ }, z.core.$strict>>;
1621
+ direction: z.ZodOptional<z.ZodEnum<{
1622
+ TB: "TB";
1623
+ TD: "TD";
1624
+ BT: "BT";
1625
+ LR: "LR";
1626
+ RL: "RL";
1627
+ }>>;
1628
+ }, z.core.$strict>, z.ZodObject<{
1629
+ type: z.ZodLiteral<"architecture">;
1630
+ layers: z.ZodArray<z.ZodObject<{
1631
+ title: z.ZodString;
1632
+ items: z.ZodArray<z.ZodString>;
1633
+ }, z.core.$strict>>;
1634
+ }, z.core.$strict>, z.ZodObject<{
1635
+ type: z.ZodLiteral<"timeline">;
1636
+ layout: z.ZodOptional<z.ZodEnum<{
1637
+ horizontal: "horizontal";
1638
+ vertical: "vertical";
1639
+ }>>;
1640
+ milestones: z.ZodArray<z.ZodObject<{
1641
+ date: z.ZodString;
1642
+ title: z.ZodString;
1643
+ desc: z.ZodOptional<z.ZodString>;
1644
+ highlight: z.ZodOptional<z.ZodBoolean>;
1645
+ }, z.core.$strict>>;
1646
+ }, z.core.$strict>, z.ZodObject<{
1647
+ type: z.ZodLiteral<"comparison">;
1648
+ columns: z.ZodArray<z.ZodString>;
1649
+ rows: z.ZodArray<z.ZodObject<{
1650
+ label: z.ZodString;
1651
+ cells: z.ZodArray<z.ZodString>;
1652
+ }, z.core.$strict>>;
1653
+ }, z.core.$strict>, z.ZodObject<{
1654
+ type: z.ZodLiteral<"icon_cards">;
1655
+ items: z.ZodArray<z.ZodObject<{
1656
+ icon: z.ZodEnum<{
1657
+ [x: string]: string;
1658
+ }>;
1659
+ title: z.ZodString;
1660
+ text: z.ZodString;
1661
+ }, z.core.$strict>>;
1662
+ }, z.core.$strict>, z.ZodObject<{
1663
+ type: z.ZodLiteral<"row_cards">;
1664
+ items: z.ZodArray<z.ZodObject<{
1665
+ icon: z.ZodOptional<z.ZodEnum<{
1666
+ [x: string]: string;
1667
+ }>>;
1668
+ title: z.ZodString;
1669
+ text: z.ZodOptional<z.ZodString>;
1670
+ sub: z.ZodOptional<z.ZodString>;
1671
+ highlight: z.ZodOptional<z.ZodBoolean>;
1672
+ }, z.core.$strict>>;
1673
+ }, z.core.$strict>, z.ZodObject<{
1674
+ type: z.ZodLiteral<"steps">;
1675
+ items: z.ZodArray<z.ZodObject<{
1676
+ title: z.ZodString;
1677
+ text: z.ZodString;
1678
+ }, z.core.$strict>>;
1679
+ }, z.core.$strict>, z.ZodObject<{
1680
+ type: z.ZodLiteral<"rings">;
1681
+ items: z.ZodArray<z.ZodObject<{
1682
+ label: z.ZodString;
1683
+ desc: z.ZodOptional<z.ZodString>;
1684
+ }, z.core.$strict>>;
1685
+ }, z.core.$strict>, z.ZodObject<{
1686
+ type: z.ZodLiteral<"numbered_cards">;
1687
+ items: z.ZodArray<z.ZodObject<{
1688
+ title: z.ZodString;
1689
+ text: z.ZodOptional<z.ZodString>;
1690
+ sub: z.ZodOptional<z.ZodString>;
1691
+ }, z.core.$strict>>;
1692
+ }, z.core.$strict>, z.ZodObject<{
1693
+ type: z.ZodLiteral<"roadmap">;
1694
+ items: z.ZodArray<z.ZodObject<{
1695
+ title: z.ZodString;
1696
+ period: z.ZodOptional<z.ZodString>;
1697
+ rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
1698
+ label: z.ZodString;
1699
+ value: z.ZodString;
1700
+ }, z.core.$strict>>>;
1701
+ }, z.core.$strict>>;
1702
+ }, z.core.$strict>, z.ZodObject<{
1703
+ type: z.ZodLiteral<"matrix">;
1704
+ x_title: z.ZodOptional<z.ZodString>;
1705
+ y_title: z.ZodOptional<z.ZodString>;
1706
+ cols: z.ZodNumber;
1707
+ items: z.ZodArray<z.ZodObject<{
1708
+ title: z.ZodString;
1709
+ tag: z.ZodOptional<z.ZodString>;
1710
+ tone: z.ZodOptional<z.ZodEnum<{
1711
+ info: "info";
1712
+ accent: "accent";
1713
+ neutral: "neutral";
1714
+ }>>;
1715
+ }, z.core.$strict>>;
1716
+ }, z.core.$strict>, z.ZodObject<{
1717
+ type: z.ZodLiteral<"insight_panel">;
1718
+ title: z.ZodString;
1719
+ rows: z.ZodArray<z.ZodObject<{
1720
+ label: z.ZodString;
1721
+ text: z.ZodString;
1722
+ }, z.core.$strict>>;
1723
+ footnote: z.ZodOptional<z.ZodString>;
1724
+ }, z.core.$strict>, z.ZodObject<{
1725
+ type: z.ZodLiteral<"verdict_banner">;
1726
+ text: z.ZodString;
1727
+ tone: z.ZodEnum<{
1728
+ neutral: "neutral";
1729
+ positive: "positive";
1730
+ warning: "warning";
1731
+ }>;
1732
+ icon: z.ZodOptional<z.ZodEnum<{
1733
+ [x: string]: string;
1734
+ }>>;
1735
+ }, z.core.$strict>, z.ZodObject<{
1736
+ type: z.ZodLiteral<"citation">;
1737
+ sources: z.ZodArray<z.ZodObject<{
1738
+ label: z.ZodString;
1739
+ url: z.ZodOptional<z.ZodString>;
1740
+ ref: z.ZodOptional<z.ZodString>;
1741
+ }, z.core.$strict>>;
1742
+ }, z.core.$strict>, z.ZodObject<{
1743
+ type: z.ZodLiteral<"image">;
1744
+ asset_id: z.ZodString;
1745
+ caption: z.ZodOptional<z.ZodString>;
1746
+ fit: z.ZodDefault<z.ZodEnum<{
1747
+ cover: "cover";
1748
+ contain: "contain";
1749
+ }>>;
1750
+ }, z.core.$strict>, z.ZodObject<{
1751
+ type: z.ZodLiteral<"image_grid">;
1752
+ items: z.ZodArray<z.ZodObject<{
1753
+ asset_id: z.ZodString;
1754
+ caption: z.ZodOptional<z.ZodString>;
1755
+ }, z.core.$strict>>;
1756
+ emphasis: z.ZodOptional<z.ZodEnum<{
1757
+ none: "none";
1758
+ first: "first";
1759
+ }>>;
1760
+ }, z.core.$strict>, z.ZodObject<{
1761
+ type: z.ZodLiteral<"image_compare">;
1762
+ left: z.ZodObject<{
1763
+ asset_id: z.ZodString;
1764
+ label: z.ZodString;
1765
+ }, z.core.$strict>;
1766
+ right: z.ZodObject<{
1767
+ asset_id: z.ZodString;
1768
+ label: z.ZodString;
1769
+ }, z.core.$strict>;
1770
+ style: z.ZodOptional<z.ZodEnum<{
1771
+ vs: "vs";
1772
+ before_after: "before_after";
1773
+ }>>;
1774
+ }, z.core.$strict>, z.ZodObject<{
1775
+ type: z.ZodLiteral<"swot">;
1776
+ strengths: z.ZodArray<z.ZodString>;
1777
+ weaknesses: z.ZodArray<z.ZodString>;
1778
+ opportunities: z.ZodArray<z.ZodString>;
1779
+ threats: z.ZodArray<z.ZodString>;
1780
+ labels: z.ZodOptional<z.ZodObject<{
1781
+ strengths: z.ZodOptional<z.ZodString>;
1782
+ weaknesses: z.ZodOptional<z.ZodString>;
1783
+ opportunities: z.ZodOptional<z.ZodString>;
1784
+ threats: z.ZodOptional<z.ZodString>;
1785
+ }, z.core.$strict>>;
1786
+ }, z.core.$strict>, z.ZodObject<{
1787
+ type: z.ZodLiteral<"bmc">;
1788
+ key_partners: z.ZodArray<z.ZodString>;
1789
+ key_activities: z.ZodArray<z.ZodString>;
1790
+ key_resources: z.ZodArray<z.ZodString>;
1791
+ value_propositions: z.ZodArray<z.ZodString>;
1792
+ customer_relationships: z.ZodArray<z.ZodString>;
1793
+ channels: z.ZodArray<z.ZodString>;
1794
+ customer_segments: z.ZodArray<z.ZodString>;
1795
+ cost_structure: z.ZodArray<z.ZodString>;
1796
+ revenue_streams: z.ZodArray<z.ZodString>;
1797
+ }, z.core.$strict>, z.ZodObject<{
1798
+ type: z.ZodLiteral<"waterfall">;
1799
+ items: z.ZodArray<z.ZodObject<{
1800
+ label: z.ZodString;
1801
+ value: z.ZodNumber;
1802
+ kind: z.ZodOptional<z.ZodEnum<{
1803
+ delta: "delta";
1804
+ total: "total";
1805
+ }>>;
1806
+ }, z.core.$strict>>;
1807
+ unit: z.ZodOptional<z.ZodString>;
1808
+ }, z.core.$strict>, z.ZodObject<{
1809
+ type: z.ZodLiteral<"gantt">;
1810
+ items: z.ZodArray<z.ZodObject<{
1811
+ label: z.ZodString;
1812
+ start: z.ZodNumber;
1813
+ end: z.ZodNumber;
1814
+ }, z.core.$strict>>;
1815
+ axis_labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1816
+ }, z.core.$strict>], "type">>>;
1817
+ background: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1818
+ kind: z.ZodLiteral<"color">;
1819
+ value: z.ZodString;
1820
+ }, z.core.$strict>, z.ZodObject<{
1821
+ kind: z.ZodLiteral<"gradient">;
1822
+ from: z.ZodString;
1823
+ to: z.ZodString;
1824
+ direction: z.ZodOptional<z.ZodEnum<{
1825
+ tb: "tb";
1826
+ lr: "lr";
1827
+ diagonal: "diagonal";
1828
+ }>>;
1829
+ }, z.core.$strict>, z.ZodObject<{
1830
+ kind: z.ZodLiteral<"asset">;
1831
+ asset_id: z.ZodString;
1832
+ overlay: z.ZodOptional<z.ZodObject<{
1833
+ color: z.ZodString;
1834
+ opacity: z.ZodNumber;
1835
+ }, z.core.$strict>>;
1836
+ fit: z.ZodOptional<z.ZodEnum<{
1837
+ cover: "cover";
1838
+ contain: "contain";
1839
+ }>>;
1840
+ }, z.core.$strict>], "kind">>;
1841
+ decor: z.ZodOptional<z.ZodObject<{
1842
+ kind: z.ZodEnum<{
1843
+ big_number: "big_number";
1844
+ corner_tag: "corner_tag";
1845
+ rule_line: "rule_line";
1846
+ quote_marks: "quote_marks";
1847
+ geo_dots: "geo_dots";
1848
+ }>;
1849
+ intensity: z.ZodOptional<z.ZodEnum<{
1850
+ subtle: "subtle";
1851
+ normal: "normal";
1852
+ }>>;
1853
+ text: z.ZodOptional<z.ZodString>;
1854
+ }, z.core.$strict>>;
1855
+ image_side: z.ZodOptional<z.ZodEnum<{
1856
+ left: "left";
1857
+ right: "right";
1858
+ }>>;
1859
+ footnote: z.ZodOptional<z.ZodString>;
1860
+ notes: z.ZodOptional<z.ZodString>;
1861
+ }, z.core.$strict>>;
1862
+ }, z.core.$strict>;
1863
+ type PptxIRV3 = z.infer<typeof PptxIRV3Schema>;
1864
+
1865
+ /**
1866
+ * Deterministic, pure IR v3 → v4 migration (spec §9.1). Field-for-field,
1867
+ * value-for-value per the mapping in {@link migrateNarrativeInput}'s
1868
+ * docstring — every field this function doesn't touch (`filename`, `theme`,
1869
+ * `meta`, `assets`, `brand`, `seed`, `slides`) carries across by the exact
1870
+ * same reference it came in with, unchanged (spec §9.1: "其余 IR 字段保持不
1871
+ * 变"; spec §10: no weight/budget/selection/render change is in scope for
1872
+ * this migration, ever).
1873
+ *
1874
+ * Exported from the SDK surface (`src/index.ts`) as the deterministic
1875
+ * migration primitive the `pptfast migrate` CLI command (task 2) wraps —
1876
+ * this function itself does no I/O and never runs a model, per spec §9.3's
1877
+ * "只做已声明的结构映射,不运行模型,不重写内容,不重新选择 layout".
1878
+ *
1879
+ * Takes an already-parsed `PptxIRV3` (i.e. `PptxIRV3Schema.parse(...)`'s
1880
+ * output, defaults already applied) rather than raw `unknown` JSON — schema
1881
+ * validation of the v3 input is the caller's job (the CLI parses-then-
1882
+ * migrates; `validateIr`'s own v3 path hard-rejects before ever reaching
1883
+ * this function, spec §9.3, so `validateIr` itself never calls this).
1884
+ */
1885
+ declare function migrateIrV3ToV4(v3: PptxIRV3): PptxIR;
1886
+
1218
1887
  type LayoutType = "cover" | "chapter" | "content" | "ending";
1219
1888
  interface StyleColors {
1220
1889
  bg: string;
@@ -1361,41 +2030,44 @@ declare function getInstalledThemeIds(): readonly string[];
1361
2030
  */
1362
2031
  declare function getThemeDefinition(id: string): ThemeDefinition;
1363
2032
 
1364
- declare const MODE_VALUES: readonly ["pyramid", "narrative", "instructional", "showcase", "briefing"];
1365
- declare const DELIVERY_VALUES: readonly ["text", "balanced", "presentation"];
2033
+ declare const STRATEGY_VALUES: readonly ["pyramid", "storytelling", "instructional", "showcase", "briefing"];
2034
+ declare const PACING_VALUES: readonly ["dense", "balanced", "spacious"];
1366
2035
  declare const AUDIENCE_VALUES: readonly ["executive", "technical", "customer", "public"];
1367
2036
 
1368
2037
  /**
1369
- * Narrative argument style (spec §5's five-way mode classification). Each
1370
- * mode carries a component/layout tendency set (soft-weight material,
1371
- * consumed by W4's weighted selection — nothing consumes it this wave) and
1372
- * a rhythm policy (consumed by W5's plan-validate rotation gate).
2038
+ * Narrative argument style (spec §5's five-way strategy classification,
2039
+ * renamed from "mode" spec §8.1). Each strategy carries a
2040
+ * component/layout tendency set (soft-weight material, consumed by W4's
2041
+ * weighted selection nothing consumes it this wave) and a beat policy
2042
+ * (renamed from "rhythm policy" — spec §2.3's deck/page beat split; consumed
2043
+ * by W5's spec-validate rotation gate).
1373
2044
  */
1374
- type Mode = (typeof MODE_VALUES)[number];
2045
+ type Strategy = (typeof STRATEGY_VALUES)[number];
1375
2046
  /**
1376
- * Content density budget + typographic baseline (spec §5 delivery table).
1377
- * See {@link DeliveryBudget} for the dual-attribute capacity split this axis
1378
- * is one half of.
2047
+ * Content density budget + typographic baseline (spec §5 pacing table,
2048
+ * renamed from "delivery" — spec §8.1). See {@link PacingBudget} for the
2049
+ * dual-attribute capacity split this axis is one half of.
1379
2050
  */
1380
- type Delivery = (typeof DELIVERY_VALUES)[number];
2051
+ type Pacing = (typeof PACING_VALUES)[number];
1381
2052
  /**
1382
2053
  * Tone anchor only (spec §5: audience is tone-anchoring only, no rendering
1383
2054
  * effect on the IR yet). Reserved for a future lint pass (e.g. executive ×
1384
2055
  * long paragraphs → suggest kpi_cards/verdict_banner instead). The rule set
1385
2056
  * itself is explicitly out of scope this wave (spec §10 open questions).
2057
+ * Name unchanged by the vocabulary-v4 rename (spec §4.3: "audience 保持不变").
1386
2058
  */
1387
2059
  type Audience = (typeof AUDIENCE_VALUES)[number];
1388
- interface ScenarioAxes {
1389
- readonly mode: Mode;
1390
- readonly delivery: Delivery;
2060
+ interface NarrativeProfile {
2061
+ readonly strategy: Strategy;
2062
+ readonly pacing: Pacing;
1391
2063
  readonly audience: Audience;
1392
2064
  }
1393
2065
 
1394
- interface ModeDefinition {
1395
- id: Mode;
2066
+ interface StrategyDefinition {
2067
+ id: Strategy;
1396
2068
  /**
1397
2069
  * Layout/component tendency set (soft-weight material). Filled row-for-row
1398
- * from spec §5's mode table for W4's weighted selection step (spec §6
2070
+ * from spec §5's strategy table for W4's weighted selection step (spec §6
1399
2071
  * step 4: in-set candidates get ×3 weight, out-of-set ×1 floor — not
1400
2072
  * implemented yet, this module only stores the data).
1401
2073
  *
@@ -1416,48 +2088,50 @@ interface ModeDefinition {
1416
2088
  * `svg/effective-layout.ts`, next to `resolveArchetypeId`, the sole
1417
2089
  * consumer). Deliberately a separate field from {@link tendencies} above,
1418
2090
  * not a reinterpretation of it: `tendencies` mixes component-type names and
1419
- * layout ids drawn from spec §5's mode table verbatim and also feeds W5's
1420
- * plan `focus` vocabulary gate, so narrowing its meaning here would be a
2091
+ * layout ids drawn from spec §5's strategy table verbatim and also feeds W5's
2092
+ * spec `focus` vocabulary gate, so narrowing its meaning here would be a
1421
2093
  * breaking change to an existing consumer. This field holds only
1422
2094
  * `LAYOUT_REGISTRY` content-archetype ids (`svg/layouts/registry.ts`'s
1423
2095
  * `CONTENT_LAYOUTS` keys) — cover/chapter/ending ids never appear in any
1424
- * mode's list here, which is exactly why `resolveArchetypeId`'s weighting
2096
+ * strategy's list here, which is exactly why `resolveArchetypeId`'s weighting
1425
2097
  * is a no-op for those three slide types (spec: "身份页个性来自 theme 不来自
1426
- * mode,均匀取样") without needing a slide-type special case — a weight
2098
+ * strategy,均匀取样") without needing a slide-type special case — a weight
1427
2099
  * lookup against an id that can never match falls through to the ×1 floor
1428
2100
  * for every candidate, uniform by construction. `tone-adaptive-content`
1429
- * appears in no mode's list either (spec's "万金油" call-out: it is the one
1430
- * content archetype meant to read as mode-neutral, so it always gets the
1431
- * ×1 floor too).
2101
+ * appears in no strategy's list either (spec's "万金油" call-out: it is the
2102
+ * one content archetype meant to read as strategy-neutral, so it always
2103
+ * gets the ×1 floor too).
1432
2104
  */
1433
2105
  layoutTendencies: readonly string[];
1434
2106
  /**
1435
- * Rhythm template descriptor (spec §5's per-mode rhythm-default column),
1436
- * parameterized by mode for W5's plan-validate rotation gate — e.g.
1437
- * briefing is exempt from a generic "three same-rhythm pages in a row is
1438
- * an error" rule because uniform-dense *is* briefing's correct default,
1439
- * not a violation of it (spec §5's plan-gate section calls out that a
1440
- * generic same-rhythm-streak rule would reject briefing's own default).
1441
- * Not consumed this wave.
2107
+ * Beat template descriptor (spec §5's per-strategy beat-default column,
2108
+ * renamed from "rhythm" — spec §2.3), parameterized by strategy for W5's
2109
+ * spec-validate rotation gate — e.g. briefing is exempt from a generic
2110
+ * "three same-beat pages in a row is an error" rule because uniform-dense
2111
+ * *is* briefing's correct default, not a violation of it (spec §5's
2112
+ * spec-gate section calls out that a generic same-beat-streak rule would
2113
+ * reject briefing's own default). Not consumed this wave.
1442
2114
  */
1443
- rhythmPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
2115
+ beatPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
1444
2116
  }
1445
- declare const MODE_DEFINITIONS: Record<Mode, ModeDefinition>;
2117
+ declare const STRATEGY_DEFINITIONS: Record<Strategy, StrategyDefinition>;
1446
2118
 
1447
- interface DeliveryBudget {
2119
+ interface PacingBudget {
1448
2120
  /**
1449
- * Body-text baseline, in px, at 1280×720 slide geometry (spec §5
1450
- * delivery table's body-baseline column). Wired into rendering as of W4
1451
- * task 3 (design decision 9): `src/svg/FullSlideSvg.tsx` resolves
1452
- * `DELIVERY_BUDGETS[resolveScenario(ir.scenario).delivery].bodyBaselinePx`
2121
+ * Body-text baseline, in px, at 1280×720 slide geometry (spec §5 pacing
2122
+ * table's body-baseline column). Wired into rendering as of W4 task 3
2123
+ * (design decision 9): `src/svg/FullSlideSvg.tsx` resolves
2124
+ * `PACING_BUDGETS[resolveNarrative(ir.narrative).pacing].bodyBaselinePx`
1453
2125
  * once and passes it into `buildCtx`, which stores it as
1454
2126
  * `ComponentCtx.bodyFontPx` — the sole font-size input for the
1455
2127
  * paragraph/bullets/callout trio ("正文" = continuous running text).
1456
2128
  * Every other component's own bespoke type scale, the heading system, and
1457
2129
  * quote's fixed 26px attribution line don't read this field. Since
1458
- * `balanced` (24px) is the scenario default, an omitted-scenario deck now
1459
- * renders body text at 24px, not the previous fixed 20px — the one-time,
1460
- * spec-sanctioned snapshot re-pin that landed alongside this wiring.
2130
+ * `balanced` (24px) is the narrative default, an omitted-narrative deck
2131
+ * now renders body text at 24px, not the previous fixed 20px — the
2132
+ * one-time, spec-sanctioned snapshot re-pin that landed alongside this
2133
+ * wiring (W4 task 3, unchanged since — vocabulary-v4 only renames this
2134
+ * table, it doesn't touch any of its numbers, spec §10).
1461
2135
  */
1462
2136
  bodyBaselinePx: number;
1463
2137
  /**
@@ -1476,23 +2150,24 @@ interface DeliveryBudget {
1476
2150
  * (`measureTextUnits`, CJK weight = 1.0) — a visual-width-weighted
1477
2151
  * character count, not a raw `.length`. The old physical ceiling (53,
1478
2152
  * derived from render geometry) was deleted in W3 without a replacement
1479
- * check because every delivery's editorial budget here (30/40/48) is
2153
+ * check because every pacing's editorial budget here (30/40/48) is
1480
2154
  * already strictly tighter than it — the quality gate now applies
1481
- * exactly this one number per delivery.
2155
+ * exactly this one number per pacing.
1482
2156
  */
1483
2157
  maxUnitsPerItem: number;
1484
2158
  };
1485
2159
  }
1486
2160
  /**
1487
- * Pinned to spec §5's delivery table (`bodyBaselinePx` / editorial budget /
1488
- * bullets budget columns): text 20/5/6×48, balanced 24/4/5×40,
1489
- * presentation 32/3/4×30.
2161
+ * Pinned to spec §5's pacing table (`bodyBaselinePx` / editorial budget /
2162
+ * bullets budget columns): dense 20/5/6×48, balanced 24/4/5×40, spacious
2163
+ * 32/3/4×30 — same three rows as the pre-rename `text`/`balanced`/
2164
+ * `presentation` delivery table, values byte-for-byte unchanged (spec §10).
1490
2165
  */
1491
- declare const DELIVERY_BUDGETS: Record<Delivery, DeliveryBudget>;
2166
+ declare const PACING_BUDGETS: Record<Pacing, PacingBudget>;
1492
2167
 
1493
- interface ScenarioPreset {
2168
+ interface NarrativePreset {
1494
2169
  id: string;
1495
- axes: ScenarioAxes;
2170
+ axes: NarrativeProfile;
1496
2171
  /**
1497
2172
  * Soft theme recommendations — a suggestion, never a hard constraint
1498
2173
  * (spec §5). Surfaced in workflow step ① so an agent can open with a
@@ -1502,27 +2177,33 @@ interface ScenarioPreset {
1502
2177
  */
1503
2178
  themeRecommendations: readonly string[];
1504
2179
  }
1505
- /** Pinned to spec §5's 7 named presets and their theme recommendation table. */
1506
- declare const SCENARIO_PRESETS: Record<string, ScenarioPreset>;
1507
2180
  /**
1508
- * = `SCENARIO_PRESETS.general.axes` (briefing × balanced × public) the
1509
- * global default when scenario is omitted entirely (spec §5's defaults
2181
+ * Pinned to spec §5's 7 named presets and their theme recommendation table.
2182
+ * Preset ids are unchanged by the vocabulary-v4 rename (spec §5: "预设 ID
2183
+ * 保持不变") — only each preset's internal axes keys/values follow the
2184
+ * strategy/pacing rename (e.g. `annual-review`'s `mode: "narrative"` is now
2185
+ * `strategy: "storytelling"`, spec §5's own worked example).
2186
+ */
2187
+ declare const NARRATIVE_PRESETS: Record<string, NarrativePreset>;
2188
+ /**
2189
+ * = `NARRATIVE_PRESETS.general.axes` (briefing × balanced × public) — the
2190
+ * global default when narrative is omitted entirely (spec §5's defaults
1510
2191
  * chain).
1511
2192
  */
1512
- declare const DEFAULT_SCENARIO: ScenarioAxes;
2193
+ declare const DEFAULT_NARRATIVE: NarrativeProfile;
1513
2194
  /**
1514
- * Resolve a scenario input down to concrete axes, per spec §5's design
2195
+ * Resolve a narrative input down to concrete axes, per spec §5's design
1515
2196
  * principle "omission gets the default, a typo is a hard error" (weak-model
1516
2197
  * friendly: a model that leaves a field out gets a sane deck, a model that
1517
2198
  * misspells a value gets a loud, actionable error instead of a silently
1518
2199
  * wrong deck):
1519
2200
  *
1520
- * - `undefined` → {@link DEFAULT_SCENARIO} (the `general` preset's axes)
2201
+ * - `undefined` → {@link DEFAULT_NARRATIVE} (the `general` preset's axes)
1521
2202
  * - a preset id string → that preset's axes (unknown id throws
1522
2203
  * {@link PptfastError}, listing the available preset ids)
1523
2204
  * - a partial axes object → each axis defaults independently
1524
- * (mode → "briefing", delivery → "balanced", audience → "public" — these
1525
- * happen to equal `DEFAULT_SCENARIO`'s values because `general` *is* that
2205
+ * (strategy → "briefing", pacing → "balanced", audience → "public" — these
2206
+ * happen to equal `DEFAULT_NARRATIVE`'s values because `general` *is* that
1526
2207
  * exact combination, but the fallback here is per-axis, not "any omitted
1527
2208
  * axis falls back to the whole default object")
1528
2209
  *
@@ -1530,50 +2211,64 @@ declare const DEFAULT_SCENARIO: ScenarioAxes;
1530
2211
  * always throws {@link PptfastError} (never silently ignored or dropped) —
1531
2212
  * omission and a typo are different intents, and only the former has a
1532
2213
  * reasonable default.
2214
+ *
2215
+ * Renamed from `resolveScenario` (spec §8.1). Callers that still hold a
2216
+ * pre-rename `mode`/`delivery` shaped input (e.g. a v3 IR's `scenario`
2217
+ * field) must migrate it first — see `migrateIrV3ToV4`
2218
+ * (`src/ir/migrate.ts`) for the deterministic field/value mapping. A
2219
+ * v4-track document that still writes the old field/value spelling gets no
2220
+ * such rescue (spec §16, reversing the now-superseded §15.4): this function
2221
+ * hard-errors on it, same as any other unknown axis key or value.
1533
2222
  */
1534
- declare function resolveScenario(input: string | Partial<ScenarioAxes> | undefined): ScenarioAxes;
2223
+ declare function resolveNarrative(input: string | Partial<NarrativeProfile> | undefined): NarrativeProfile;
1535
2224
 
1536
2225
  /**
1537
- * Deck plan schema + validation (spec §5 "plan artifact and hard gates", W5 task 2).
2226
+ * Deck spec schema + validation (spec §5 "plan artifact and hard gates", W5
2227
+ * task 2 — renamed to "Deck Spec" per the vocabulary-v4 rename, spec §6/§8.1;
2228
+ * "spec §N" citations throughout this file that predate this rename still
2229
+ * point at that original W5 design doc, not this rename's own spec — left
2230
+ * as historical citations, not renumbered).
1538
2231
  *
1539
- * A plan is a workflow artifact, not a render prerequisite (spec §5's
1540
- * "escape hatch": a bare IR v3 renders directly, plan validate is a separate,
1541
- * optional gate for the plan-authoring stage of the six-phase workflow).
1542
- * This module stays pure and Node-free — no `fs`, no CLI concerns — so it
1543
- * can sit in `src/index.ts`'s dependency closure exactly like `src/ir` and
1544
- * `src/scenario` already do (`AGENTS.md`'s layout rule). `src/cli/commands.ts`
1545
- * owns the one Node-touching wrapper (`runPlanValidate`: read file, call
1546
- * {@link validatePlan}, format the result).
2232
+ * A deck spec is a workflow artifact, not a render prerequisite (spec §5's
2233
+ * "escape hatch": a bare IR v3 renders directly, `spec validate` is a
2234
+ * separate, optional gate for the spec-authoring stage of the six-phase
2235
+ * workflow). This module stays pure and Node-free — no `fs`, no CLI concerns
2236
+ * — so it can sit in `src/index.ts`'s dependency closure exactly like
2237
+ * `src/ir` and `src/narrative` already do (`AGENTS.md`'s layout rule).
2238
+ * `src/cli/commands.ts` owns the one Node-touching wrapper
2239
+ * (`runSpecValidate`: read file, call {@link validateSpec}, format the
2240
+ * result).
1547
2241
  *
1548
2242
  * Design mirrors `api.ts`'s `validateIr`/`ValidateResult` throughout
1549
2243
  * (structural zod pass first, then a sequential chain of hard-gate
1550
2244
  * categories, each short-circuiting the chain on its own failure — see
1551
- * {@link validatePlan}'s own comment for why): same overall shape, adapted
1552
- * for plan pages being keyed by an author-assigned `id` instead of IR's
2245
+ * {@link validateSpec}'s own comment for why): same overall shape, adapted
2246
+ * for spec pages being keyed by an author-assigned `id` instead of IR's
1553
2247
  * positional slide index.
1554
2248
  */
1555
2249
 
1556
2250
  /**
1557
2251
  * Mirrors `SlideSchema.type` / `SlideType` (`ir/index.ts`,
1558
2252
  * `svg/layouts/registry.ts`) exactly. Kept as an independent literal tuple
1559
- * here (not imported from either) — a plan page's `type` is a 4-value enum
2253
+ * here (not imported from either) — a page spec's `type` is a 4-value enum
1560
2254
  * on its own schema, not a re-export of IR's — but the `satisfies` clause
1561
2255
  * makes any future drift between the two a compile error instead of a
1562
2256
  * silent mismatch.
1563
2257
  */
1564
2258
  declare const PAGE_TYPES: readonly ["cover", "chapter", "content", "ending"];
1565
- declare const RHYTHM_VALUES: readonly ["anchor", "dense", "breathing"];
1566
- type PlanPageType = (typeof PAGE_TYPES)[number];
1567
- type PlanRhythm = (typeof RHYTHM_VALUES)[number];
2259
+ declare const BEAT_VALUES: readonly ["anchor", "dense", "breathing"];
2260
+ type PageSpecType = (typeof PAGE_TYPES)[number];
2261
+ type PageBeat = (typeof BEAT_VALUES)[number];
1568
2262
  /**
1569
- * A single plan page (spec §5). `type`/`heading` are required — unlike IR's
1570
- * own `SlideSchema` (where both default/omit for weak-model friendliness),
1571
- * a plan is the authoring artifact those fields get *locked* from at
1572
- * assemble time (W5 task 3), so leaving either implicit here would defeat
1573
- * the point. `rhythm`/`focus`/`summary` stay optional per spec §5's defaults
1574
- * chain ("rhythm omitted → auto-rotates by page position — focus/summary/layout/slot can all be omitted").
2263
+ * A single page spec (spec §5, §6). `type`/`heading` are required — unlike
2264
+ * IR's own `SlideSchema` (where both default/omit for weak-model
2265
+ * friendliness), a deck spec is the authoring artifact those fields get
2266
+ * *locked* from at assemble time (W5 task 3), so leaving either implicit
2267
+ * here would defeat the point. `beat`/`focus`/`summary` stay optional per
2268
+ * spec §5's defaults chain ("beat omitted → auto-rotates by page position —
2269
+ * focus/summary/layout/slot can all be omitted").
1575
2270
  */
1576
- declare const PlanPageSchema: z.ZodObject<{
2271
+ declare const PageSpecSchema: z.ZodObject<{
1577
2272
  id: z.ZodString;
1578
2273
  type: z.ZodEnum<{
1579
2274
  cover: "cover";
@@ -1582,7 +2277,7 @@ declare const PlanPageSchema: z.ZodObject<{
1582
2277
  ending: "ending";
1583
2278
  }>;
1584
2279
  heading: z.ZodString;
1585
- rhythm: z.ZodOptional<z.ZodEnum<{
2280
+ beat: z.ZodOptional<z.ZodEnum<{
1586
2281
  anchor: "anchor";
1587
2282
  dense: "dense";
1588
2283
  breathing: "breathing";
@@ -1590,19 +2285,27 @@ declare const PlanPageSchema: z.ZodObject<{
1590
2285
  focus: z.ZodOptional<z.ZodString>;
1591
2286
  summary: z.ZodOptional<z.ZodString>;
1592
2287
  }, z.core.$strict>;
1593
- type PlanPage = z.infer<typeof PlanPageSchema>;
2288
+ type PageSpec = z.infer<typeof PageSpecSchema>;
1594
2289
  /**
1595
- * Top-level deck plan shape (spec §5). `scenario`/`theme` deliberately have
1596
- * no schema-level `.default(...)` — same reasoning as `PptxIRSchema`'s own
1597
- * `scenario` field (`ir/index.ts`): the resolved value is never baked back
1598
- * into the parsed shape, {@link validatePlan} (here) and, later, assemble
1599
- * (W5 task 3) each resolve it themselves. `seed` is accepted but entirely
1600
- * unexamined by this module — "not validatePlan's concern" (spec's own wording):
1601
- * assemble generates and suggests writing one back on first materialization.
2290
+ * Top-level deck spec shape (spec §5, §6). `narrative`/`theme` deliberately
2291
+ * have no schema-level `.default(...)` — same reasoning as `PptxIRSchema`'s
2292
+ * own `narrative` field (`ir/index.ts`): the resolved value is never baked
2293
+ * back into the parsed shape, {@link validateSpec} (here) and, later,
2294
+ * assemble (W5 task 3) each resolve it themselves. `seed` is accepted but
2295
+ * entirely unexamined by this module — "not validateSpec's concern" (spec's
2296
+ * own wording): assemble generates and suggests writing one back on first
2297
+ * materialization.
2298
+ *
2299
+ * `version` stays the literal `"1"` (unchanged value) but now carries an
2300
+ * independent Deck Spec versioning scheme (spec §6: "`deck.spec.json` 使用
2301
+ * 独立的 spec 版本 1。它是新工件,不继承 `deck.plan.json` 的版本号语义") —
2302
+ * this "1" is the Deck Spec artifact's own first version, not a continuation
2303
+ * of the old deck-plan artifact's version counter, even though the digit is
2304
+ * the same.
1602
2305
  */
1603
- declare const DeckPlanSchema: z.ZodObject<{
2306
+ declare const DeckSpecSchema: z.ZodObject<{
1604
2307
  version: z.ZodDefault<z.ZodLiteral<"1">>;
1605
- scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
2308
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1606
2309
  theme: z.ZodOptional<z.ZodString>;
1607
2310
  filename: z.ZodOptional<z.ZodString>;
1608
2311
  seed: z.ZodOptional<z.ZodNumber>;
@@ -1659,7 +2362,7 @@ declare const DeckPlanSchema: z.ZodObject<{
1659
2362
  ending: "ending";
1660
2363
  }>;
1661
2364
  heading: z.ZodString;
1662
- rhythm: z.ZodOptional<z.ZodEnum<{
2365
+ beat: z.ZodOptional<z.ZodEnum<{
1663
2366
  anchor: "anchor";
1664
2367
  dense: "dense";
1665
2368
  breathing: "breathing";
@@ -1668,10 +2371,10 @@ declare const DeckPlanSchema: z.ZodObject<{
1668
2371
  summary: z.ZodOptional<z.ZodString>;
1669
2372
  }, z.core.$strict>>;
1670
2373
  }, z.core.$strict>;
1671
- type DeckPlan = z.infer<typeof DeckPlanSchema>;
1672
- /** JSON Schema for the deck plan — feed this to a model before it writes a plan (see `pptfast schema --plan`). */
1673
- declare function planJsonSchema(): Record<string, unknown>;
1674
- interface PlanValidationIssue {
2374
+ type DeckSpec = z.infer<typeof DeckSpecSchema>;
2375
+ /** JSON Schema for the deck spec — feed this to a model before it writes one (see `pptfast schema --spec`). */
2376
+ declare function specJsonSchema(): Record<string, unknown>;
2377
+ interface SpecValidationIssue {
1675
2378
  path: string;
1676
2379
  message: string;
1677
2380
  /** The offending page's `id`, when the issue is scoped to one specific
@@ -1680,53 +2383,57 @@ interface PlanValidationIssue {
1680
2383
  * whose own `id` itself failed to parse. */
1681
2384
  pageId?: string;
1682
2385
  }
1683
- interface PlanValidateResult {
2386
+ interface SpecValidateResult {
1684
2387
  ok: boolean;
1685
- plan?: DeckPlan;
1686
- errors: PlanValidationIssue[];
2388
+ spec?: DeckSpec;
2389
+ errors: SpecValidationIssue[];
1687
2390
  }
1688
- declare function formatPlanIssues(errors: PlanValidationIssue[]): string;
2391
+ declare function formatSpecIssues(errors: SpecValidationIssue[]): string;
1689
2392
  /**
1690
- * Plan-level theme default (spec §5's defaults chain: "theme omitted →
2393
+ * Deck-spec-level theme default (spec §5's defaults chain: "theme omitted →
1691
2394
  * consulting") — the same default IR's own `theme.id` field carries
1692
2395
  * (`ThemeSchema` in `ir/index.ts`). Exported so a caller already holding a
1693
- * validated {@link DeckPlan} (the CLI's OK-summary line) doesn't re-derive
2396
+ * validated {@link DeckSpec} (the CLI's OK-summary line) doesn't re-derive
1694
2397
  * the fallback itself.
1695
2398
  */
1696
- declare function resolvePlanThemeId(plan: DeckPlan): string;
2399
+ declare function resolveSpecThemeId(spec: DeckSpec): string;
1697
2400
  /**
1698
- * Deck-level page-count range per delivery (spec §5's delivery table,
1699
- * initial values — "text 8-30 / balanced 6-24 / presentation 4-16", not yet
1700
- * tuned against real usage). Independent of `DELIVERY_BUDGETS`
1701
- * (`scenario/index.ts`, per-slide component-count/bullets editorial
2401
+ * Deck-level page-count range per pacing (spec §5's pacing table,
2402
+ * initial values — "dense 8-30 / balanced 6-24 / spacious 4-16", not yet
2403
+ * tuned against real usage). Independent of `PACING_BUDGETS`
2404
+ * (`narrative/index.ts`, per-slide component-count/bullets editorial
1702
2405
  * budget) — this is a separate, deck-wide page-count concern the spec calls
1703
- * out as its own hard gate ("page count vs. delivery recommended range").
2406
+ * out as its own hard gate ("page count vs. pacing recommended range").
2407
+ * Message wording renamed from "delivery" to "pacing" (vocabulary-v4
2408
+ * residual, routed from the task 1 review) — the axis itself was already
2409
+ * `Pacing` at the type level, this closes the last stale word in the
2410
+ * error text.
1704
2411
  */
1705
- declare const PLAN_PAGE_COUNT_RANGE: Record<Delivery, {
2412
+ declare const SPEC_PAGE_COUNT_RANGE: Record<Pacing, {
1706
2413
  min: number;
1707
2414
  max: number;
1708
2415
  }>;
1709
2416
  /**
1710
- * Validate raw JSON against the plan schema, then — once it parses — run the
2417
+ * Validate raw JSON against the spec schema, then — once it parses — run the
1711
2418
  * spec §5 hard-gate chain. Mirrors `validateIr`'s (`api.ts`) overall shape: a
1712
2419
  * structural zod pass first, then a sequence of isolated hard-gate
1713
2420
  * categories, each short-circuiting the whole chain on its own failure
1714
2421
  * (rather than accumulating errors across categories) so a later category
1715
2422
  * never has to guess at what an earlier, already-broken one would have
1716
- * meant — e.g. rhythm-rotation and page-count both need a resolved
1717
- * scenario, so nothing past the scenario/theme stage runs until that
1718
- * resolves cleanly. Every plan-gate philosophy here is "hard block, no soft
1719
- * warning" (spec §5's "escape hatch" section — a plan that doesn't fit this shape
2423
+ * meant — e.g. beat-rotation and page-count both need a resolved
2424
+ * narrative, so nothing past the narrative/theme stage runs until that
2425
+ * resolves cleanly. Every spec-gate philosophy here is "hard block, no soft
2426
+ * warning" (spec §5's "escape hatch" section — a spec that doesn't fit this shape
1720
2427
  * should be authored as bare IR instead, not warned-and-shipped).
1721
2428
  */
1722
- declare function validatePlan(input: unknown): PlanValidateResult;
2429
+ declare function validateSpec(input: unknown): SpecValidateResult;
1723
2430
 
1724
2431
  /**
1725
- * One page's fillable content — everything a plan page's `id` does *not*
1726
- * already lock in. Deliberately excludes `type`/`heading` (plan-owned, see
2432
+ * One page's fillable content — everything a page spec's `id` does *not*
2433
+ * already lock in. Deliberately excludes `type`/`heading` (spec-owned, see
1727
2434
  * {@link assembleDeck}'s locked-field gate) and `subheading`/`decor`
1728
2435
  * (legitimate `Slide` fields, but outside this record's shape by spec §7's
1729
- * own layout — "pages/<id>.json contains only components" — a plan/pages deck can't
2436
+ * own layout — "pages/<id>.json contains only components" — a spec/pages deck can't
1730
2437
  * author either one — a hand-authored bare IR still can). Every field here is
1731
2438
  * a same-name, same-shape subset of `Slide`'s own optional fields
1732
2439
  * (`../ir`'s `SlideSchema`) — reused, not redeclared, so the two can't drift.
@@ -1738,15 +2445,16 @@ interface PageContent {
1738
2445
  background?: BackgroundSpec;
1739
2446
  image_side?: "left" | "right";
1740
2447
  footnote?: string;
2448
+ notes?: string;
1741
2449
  }
1742
2450
  interface AssembleResult {
1743
2451
  ir: PptxIR;
1744
2452
  /**
1745
- * Set only when `plan.seed` was absent and {@link assembleDeck} generated
2453
+ * Set only when `spec.seed` was absent and {@link assembleDeck} generated
1746
2454
  * one deterministically (see the seed section of this function's doc
1747
- * comment) — `undefined` when `plan.seed` was already present and simply
2455
+ * comment) — `undefined` when `spec.seed` was already present and simply
1748
2456
  * passed through. The CLI shell (W5 task 5) is the one that acts on this:
1749
- * suggest writing the value back into the plan file, never rewrite it
2457
+ * suggest writing the value back into the spec file, never rewrite it
1750
2458
  * itself (assemble stays a pure function, no fs side effects here).
1751
2459
  */
1752
2460
  generatedSeed?: number;
@@ -1763,20 +2471,20 @@ interface AssembleResult {
1763
2471
  materializedLayoutCount?: number;
1764
2472
  }
1765
2473
  /**
1766
- * Assemble a validated plan plus a per-page content record into a renderable
1767
- * IR. See this module's own top comment for the overall shape. Step numbers
1768
- * below match the W5 task-3 brief's own numbered "inject semantics" list verbatim —
1769
- * kept in that exact order because two of them (locked-field vs. orphan)
1770
- * both throw and their relative order is otherwise unobservable from either
1771
- * doc comment alone.
2474
+ * Assemble a validated deck spec plus a per-page content record into a
2475
+ * renderable IR. See this module's own top comment for the overall shape.
2476
+ * Step numbers below match the W5 task-3 brief's own numbered "inject
2477
+ * semantics" list verbatim — kept in that exact order because two of them
2478
+ * (locked-field vs. orphan) both throw and their relative order is
2479
+ * otherwise unobservable from either doc comment alone.
1772
2480
  *
1773
- * 1. `plan` is `unknown` (same boundary `validatePlan` itself has — a plan
2481
+ * 1. `spec` is `unknown` (same boundary `validateSpec` itself has — a spec
1774
2482
  * is almost always freshly `JSON.parse`d off disk by the caller) —
1775
2483
  * invalid shape or a failed hard gate throws {@link PptfastError} with
1776
- * `validatePlan`'s own formatted issue list, not a re-derived message.
2484
+ * `validateSpec`'s own formatted issue list, not a re-derived message.
1777
2485
  * 2. Shape guard + locked-field protection: a `pages[id]` entry must first be
1778
2486
  * a plain object — not `null`, an array, or a primitive — else throws.
1779
- * `pages` is `unknown`-shaped off disk same as `plan` itself (step 1), so
2487
+ * `pages` is `unknown`-shaped off disk same as `spec` itself (step 1), so
1780
2488
  * a JSON `null`/string/array content value is a real possibility, not
1781
2489
  * just a type-system hole, and `Object.hasOwn` throws its own
1782
2490
  * uninformative native `TypeError` on `null` (and silently no-ops on a
@@ -1790,63 +2498,69 @@ interface AssembleResult {
1790
2498
  * `"heading": null`-turned-`undefined` or a copy-pasted empty key is
1791
2499
  * exactly the drift this gate exists to catch instead of silently
1792
2500
  * ignoring.
1793
- * 3. Orphan keys: a `pages` entry whose id isn't any plan page's id. Listed
2501
+ * 3. Orphan keys: a `pages` entry whose id isn't any spec page's id. Listed
1794
2502
  * together with a fix suggestion, checked only after every present page
1795
2503
  * has cleared the locked-field gate (step 2) — an orphan file that
1796
2504
  * *also* happens to redeclare `heading` reports as locked-field first.
1797
- * 4. Missing pages (a plan id with no `pages` entry) become a placeholder
2505
+ * 4. Missing pages (a spec id with no `pages` entry) become a placeholder
1798
2506
  * slide — never an error. Spec §7's own words: "assemble's precise
1799
2507
  * semantics — a missing page always succeeds (placeholder), a structural
1800
- * contradiction (orphan file / bad plan / id conflict) errors". A
2508
+ * contradiction (orphan file / bad spec / id conflict) errors". A
1801
2509
  * declared `summary` becomes the placeholder's `subheading` (the one spot
1802
- * `summary` — otherwise a plan-only anchor, see step 5 — does reach the
2510
+ * `summary` — otherwise a spec-only anchor, see step 5 — does reach the
1803
2511
  * IR) so a `--draft` preview of an unfilled deck still reads as more than
1804
2512
  * a bare "Untitled".
1805
- * 5. Present pages become a full slide: `id`/`type`/`heading` from the plan
1806
- * page (never the content record — see step 2), plus whichever of
1807
- * {@link PageContent}'s six fields the content record actually set.
1808
- * `rhythm`/`focus`/`summary` never reach the IR for a present page —
1809
- * they are plan-only authoring anchors (rhythm/focus steer a future
2513
+ * 5. Present pages become a full slide: `id`/`type`/`heading` from the page
2514
+ * spec (never the content record — see step 2), plus whichever of
2515
+ * {@link PageContent}'s seven fields the content record actually set.
2516
+ * `beat`/`focus`/`summary` never reach the IR for a present page —
2517
+ * they are spec-only authoring anchors (beat/focus steer a future
1810
2518
  * fill/select step, summary is "for the fill step's own reading only"), not slide content.
1811
- * 6. Top-level: `version` is always the literal `"3"` (IR's own version,
1812
- * unrelated to the plan's own `version: "1"`). `scenario`/`theme`/
1813
- * `filename`/`brand`/`meta`/`seed` (step 7) carry over from the plan when
2519
+ * 6. Top-level: `version` is always the literal `"4"` (IR's own version,
2520
+ * unrelated to the deck spec's own `version: "1"`) — the deck spec's own
2521
+ * `narrative` field (renamed this task from `scenario`, spec §8.1's
2522
+ * `DeckPlan`→`DeckSpec` rename, task 2) carries across into the v4 IR's
2523
+ * own `narrative` field, its value already in the new strategy/pacing
2524
+ * vocabulary (vocabulary-v4 rename, task 1 — `plan/index.ts`'s own
2525
+ * `resolveNarrative` call already validates it against that vocabulary
2526
+ * before this function ever runs).
2527
+ * `theme`/`filename`/`brand`/`meta`/`seed` (step 7) carry over from the spec when
1814
2528
  * present. When absent, this function omits the field from the raw
1815
2529
  * object it hands to {@link PptxIRSchema} rather than re-deriving IR's
1816
2530
  * own default value a second time — one default source of truth, the
1817
2531
  * schema itself (e.g. `theme` omitted here becomes `{ id: "consulting" }`,
1818
2532
  * exactly like a bare hand-authored IR that never mentions theme at all —
1819
2533
  * not a value this function needs to know).
1820
- * 7. Seed: `plan.seed` present → passed through, `generatedSeed` stays
2534
+ * 7. Seed: `spec.seed` present → passed through, `generatedSeed` stays
1821
2535
  * `undefined` on the result. Absent → {@link generateSeed} derives one
1822
- * from `filename` + the plan's own ordered page-id list (never page
2536
+ * from `filename` + the spec's own ordered page-id list (never page
1823
2537
  * *content* — see that function's own doc comment for why), written to
1824
2538
  * `ir.seed` *and* returned as `generatedSeed` so a CLI shell can suggest
1825
- * writing it back into the plan file (this function never touches disk
2539
+ * writing it back into the spec file (this function never touches disk
1826
2540
  * itself).
1827
2541
  * 8. Idempotence: every step above is a pure function of its inputs (no
1828
2542
  * randomness, no wall-clock, no reliance on unordered iteration) — two
1829
- * calls with structurally-equal `plan`/`pages` produce deep-equal
2543
+ * calls with structurally-equal `spec`/`pages` produce deep-equal
1830
2544
  * results, `generatedSeed` included. Exercised directly by this module's
1831
2545
  * test suite rather than asserted here.
1832
2546
  */
1833
- declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>): AssembleResult;
2547
+ declare function assembleDeck(spec: unknown, pages: Record<string, PageContent>): AssembleResult;
1834
2548
  /**
1835
- * Inverse of {@link assembleDeck}: reconstructs `{ plan, pages }` from an
2549
+ * Inverse of {@link assembleDeck}: reconstructs `{ spec, pages }` from an
1836
2550
  * existing IR well enough that `assembleDeck(...disassembleDeck(ir))`
1837
2551
  * reproduces every slide's content — but the map is not lossless in both
1838
2552
  * directions, only in the direction the round trip actually exercises
1839
- * (IR → plan/pages → IR). Fields with no IR-side home never survive being
2553
+ * (IR → spec/pages → IR). Fields with no IR-side home never survive being
1840
2554
  * written *to* the IR in the first place, so there is nothing here to read
1841
2555
  * back:
1842
2556
  *
1843
- * - `rhythm` and `focus` never appear on any produced {@link PlanPage} —
1844
- * both are plan-only authoring anchors with no corresponding `Slide`
2557
+ * - `beat` and `focus` never appear on any produced {@link PageSpec} —
2558
+ * both are spec-only authoring anchors with no corresponding `Slide`
1845
2559
  * field at all (see {@link assembleDeck} step 5's doc comment). Nothing
1846
2560
  * here could recover a value that was never written anywhere.
1847
2561
  * - `summary` is recovered *only* for a placeholder slide, by reversing
1848
2562
  * step 4's `summary` → `subheading` injection (`slide.subheading` back to
1849
- * `planPage.summary`). A non-placeholder slide's own `subheading` — a
2563
+ * `pageSpec.summary`). A non-placeholder slide's own `subheading` — a
1850
2564
  * legitimate, independent `Slide` field a hand-authored bare IR is free to
1851
2565
  * set — has no {@link PageContent} field to land in (spec §7's pages/
1852
2566
  * record is deliberately narrower than `Slide` itself, see that
@@ -1855,14 +2569,14 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1855
2569
  * a filled page) — so it is dropped. Same for `decor`: a real `Slide`
1856
2570
  * field, absent from `PageContent`'s shape entirely.
1857
2571
  * - `theme.style` / `theme.brand` overrides collapse to a bare theme-id
1858
- * string (`DeckPlanSchema.theme` has no shape for either) — only `theme.id`
2572
+ * string (`DeckSpecSchema.theme` has no shape for either) — only `theme.id`
1859
2573
  * survives. That `theme.brand` is `ThemeSchema.brand` (`BrandConfigSchema`
1860
2574
  * — `suppressFooterOnCardContent`/`suppressFooterRule`, footer-chrome
1861
2575
  * flags owned by the *theme*) — not to be confused with the deck-level
1862
2576
  * `brand` field below, a different, unrelated schema despite the shared
1863
2577
  * name.
1864
2578
  * - `ir.assets.images` is not part of this function's return value at all —
1865
- * `{ plan, pages }` has no `assets` field, and this module stays zero-fs
2579
+ * `{ spec, pages }` has no `assets` field, and this module stays zero-fs
1866
2580
  * by design (this file's own top comment), so it has no way to write an
1867
2581
  * `assets/` directory itself. Any `asset_id` reference inside a copied
1868
2582
  * `components`/`background` survives untouched ({@link extractPageContent}
@@ -1881,10 +2595,10 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1881
2595
  * collision: the top-level `brand` field (`BrandSchema` — `logo_asset_id` /
1882
2596
  * `position`, the deck logo/position `BrandChrome` reads,
1883
2597
  * `src/svg/BrandChrome.tsx`) is a plain passthrough on both sides
1884
- * ({@link assembleDeck} step 6 reads `plan.brand` into `ir.brand` — this
1885
- * function reads `ir.brand` back into `plan.brand` below) — carried through
1886
- * unmodified, same as `scenario`/`filename`/`seed`, never synthesized or
1887
- * dropped.
2598
+ * ({@link assembleDeck} step 6 reads `spec.brand` into `ir.brand` — this
2599
+ * function reads `ir.brand` back into `spec.brand` below) — carried through
2600
+ * unmodified, same as `narrative`/`filename`/`seed`, never
2601
+ * synthesized or dropped.
1888
2602
  *
1889
2603
  * `layout` deserves a different kind of callout: it round-trips as plain
1890
2604
  * content like any other field ({@link extractPageContent} copies
@@ -1913,7 +2627,7 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1913
2627
  * {@link UNTITLED_HEADING} for a missing/blank `heading`, and a positional
1914
2628
  * `p-<1-based-ordinal>-<type>` scheme for a missing `id` (stable across
1915
2629
  * repeated calls on the same IR — it is a pure function of slide position
1916
- * and type — but, unlike a plan-assigned id, *not* stable across inserting
2630
+ * and type — but, unlike a spec-assigned id, *not* stable across inserting
1917
2631
  * or reordering slides — out of scope here, since a bare IR with no `id` at
1918
2632
  * all has no stabler identity to fall back to in the first place).
1919
2633
  *
@@ -1932,10 +2646,44 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1932
2646
  * function) is what actually closes the vulnerability.
1933
2647
  */
1934
2648
  declare function disassembleDeck(ir: PptxIR): {
1935
- plan: DeckPlan;
2649
+ spec: DeckSpec;
1936
2650
  pages: Record<string, PageContent>;
1937
2651
  };
1938
2652
 
2653
+ /**
2654
+ * Deterministic, pure `deck.plan.json` → `deck.spec.json` migration (spec
2655
+ * §9.2, vocabulary-v4 rename, task 2). Mirrors `../ir/migrate.ts`'s
2656
+ * `migrateIrV3ToV4` in spirit — mechanical field rename only, no schema
2657
+ * validation, no model call, no content rewrite (same posture spec §9.3
2658
+ * states for the IR v3→v4 primitive: "只做已声明的结构映射,不运行模型,
2659
+ * 不重写内容,不重新选择 layout" — this function is that same contract
2660
+ * applied to the other artifact this rename touches). A caller should run
2661
+ * `validateSpec` (`./index.ts`) against the result to confirm it lands as a
2662
+ * legal deck spec, exactly as it would for a `deck.spec.json` authored by
2663
+ * hand — this function itself never parses against `DeckSpecSchema`.
2664
+ *
2665
+ * Field-for-field, value-for-value per spec §9.2's table:
2666
+ *
2667
+ * ```text
2668
+ * deck.plan.json → deck.spec.json
2669
+ * scenario → narrative
2670
+ * pages[].rhythm → pages[].beat
2671
+ * 其余字段 → 原样保留
2672
+ * ```
2673
+ *
2674
+ * Takes the raw, `JSON.parse`d `deck.plan.json` contents (`unknown`) rather
2675
+ * than an already-validated shape — unlike `migrateIrV3ToV4` (which takes an
2676
+ * already-`PptxIRV3Schema.parse`d object), there is no schema left in this
2677
+ * codebase a pre-rename plan file could validate against: `DeckSpecSchema`
2678
+ * (`./index.ts`) already requires the *post*-rename field names (`narrative`,
2679
+ * `beat`), so parsing a plan-shaped document against it would just fail on
2680
+ * the very keys this function exists to rename. Non-object input, or a
2681
+ * `pages` value that isn't an array, passes through completely unchanged —
2682
+ * reporting a malformed source file is `runMigrate`'s job
2683
+ * (`../cli/commands.ts`), not this mechanical rename step's.
2684
+ */
2685
+ declare function migrateDeckPlanToSpec(raw: unknown): unknown;
2686
+
1939
2687
  /**
1940
2688
  * `pptfast audit` finding shape (v0.3 W6, spec §7 workflow ④). `page` is
1941
2689
  * 1-based (matches `ValidationIssue.page` in `api.ts`); `slideId` is set
@@ -1946,38 +2694,72 @@ declare function disassembleDeck(ir: PptxIR): {
1946
2694
  interface AuditFinding {
1947
2695
  page: number;
1948
2696
  slideId?: string;
1949
- code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap";
2697
+ code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap" | "content-truncated" | "content-dropped";
1950
2698
  message: string;
1951
2699
  detail?: Record<string, unknown>;
1952
2700
  }
2701
+ /**
2702
+ * Which check families actually ran (audit-v2 phase B, spec §4.2/§11.6) —
2703
+ * the one piece of the spec's original metrics design that survives into
2704
+ * this wave (§11.6: metrics itself deferred, "唯一现值是 checks 结构"). This
2705
+ * is the wave's own spirit made literal: a field can only ever be
2706
+ * `"completed"` once its check family genuinely ran over every audited
2707
+ * page, never a default that silently reads as "passed". `svg` has no
2708
+ * `"not-requested"` state — the deterministic SVG audit always runs, so it
2709
+ * is always `"completed"`. `pixels` starts `"not-requested"` and only ever
2710
+ * becomes `"completed"`; there is deliberately no `"failed"` value — a
2711
+ * failed pixel audit throws (spec §11.7's "契约层"), it never reports
2712
+ * itself as a completed check that happens to carry no findings.
2713
+ */
2714
+ interface AuditChecks {
2715
+ svg: "completed";
2716
+ pixels: "not-requested" | "completed";
2717
+ }
1953
2718
  interface AuditReport {
1954
2719
  findings: AuditFinding[];
1955
2720
  pagesAudited: number;
1956
2721
  pagesSkipped: number;
2722
+ checks: AuditChecks;
2723
+ }
2724
+ /** `auditDeck`'s second parameter (audit-v2 phase B) — see that function's
2725
+ * own doc comment for the overload contract this shape backs. */
2726
+ interface AuditDeckOptions {
2727
+ pixels?: boolean;
1957
2728
  }
1958
2729
  /**
1959
- * Deterministic geometry audit over an already-valid deck (v0.3 W6, spec §7
1960
- * workflow ④): render every non-placeholder slide off-screen
1961
- * (`renderSlideSvg`, the same single-source SVG the preview and exporter
1962
- * both use) and run three check families against the rendered markup —
1963
- * overflow/out-of-bounds (reusing `svg-audit.ts`'s existing walker
1964
- * verbatim), low-contrast (WCAG relative luminance), and overlap (pairwise
1965
- * `data-audit-box` intersection). Pure no I/O, no Node dependency (see
1966
- * `parseSvg`'s doc comment) — `auditDeck` itself never calls
1967
- * `installNodePlatform()`; that's the caller's job (the CLI does it
1968
- * automatically).
1969
- *
1970
- * Advisory, not a hard gate: `validateIr` already rejects structurally
1971
- * invalid or over-dense decks before a caller ever gets this far; this
1972
- * function looks for the visual problems that can still slip through a
1973
- * valid deck at render time (an author-chosen near-background text color,
1974
- * two components whose combined content happens to collide). A non-empty
1975
- * `findings` array is a prompt for a human/agent to look, not a rejection.
2730
+ * The SDK entry point. Advisory, not a hard gate: `validateIr` already
2731
+ * rejects structurally invalid or over-dense decks before a caller ever
2732
+ * gets this far; this function looks for the visual problems that can still
2733
+ * slip through a valid deck at render time (an author-chosen near-background
2734
+ * text color, two components whose combined content happens to collide, a
2735
+ * card list that had to drop an item to fit). A non-empty `findings` array
2736
+ * is a prompt for a human/agent to look, not a rejection.
1976
2737
  *
1977
2738
  * Placeholder pages (`slide.placeholder === true`) are skipped — assemble's
1978
2739
  * stand-in for content nobody has written yet has nothing to audit, same
1979
2740
  * reasoning `checkIrQuality` already uses to skip them (`ir-quality.ts`).
2741
+ *
2742
+ * `auditDeck` itself never calls `installNodePlatform()`; that's the
2743
+ * caller's job (the CLI does it automatically).
2744
+ *
2745
+ * `opts.pixels` (audit-v2 phase B, spec §4.3/§11.7) opts into the optional
2746
+ * pixel-level contrast audit over image-backed text (`../svg/audit/pixel-audit.ts`)
2747
+ * — overloaded so the far more common omitted/`false` case keeps returning
2748
+ * a plain, synchronous `AuditReport` (spec §11.7's "语义层": the default
2749
+ * audit stays pure TS with zero added latency or Promise-wrapping), while
2750
+ * `pixels: true` returns a `Promise<AuditReport>` that resolves once the
2751
+ * rasterization pass completes. A caller holding a non-literal
2752
+ * `{ pixels: someBoolean }` won't match either overload directly (TypeScript
2753
+ * can't narrow it to one specific branch) — branch on the boolean first and
2754
+ * call this function with a literal in each arm, e.g. `opts.pixels ? await
2755
+ * auditDeck(ir, { pixels: true }) : auditDeck(ir)` (`cli/commands.ts`'s
2756
+ * `runAudit` does exactly this).
1980
2757
  */
1981
- declare function auditDeck(ir: PptxIR): AuditReport;
2758
+ declare function auditDeck(ir: PptxIR, opts?: {
2759
+ pixels?: false;
2760
+ }): AuditReport;
2761
+ declare function auditDeck(ir: PptxIR, opts: {
2762
+ pixels: true;
2763
+ }): Promise<AuditReport>;
1982
2764
 
1983
- export { AUDIENCE_VALUES, type AssembleResult, type Assets, type Audience, type AuditFinding, type AuditReport, BUILTIN_THEME_IDS, type BackgroundSpec, type BrandConfig, BrandConfigSchema, COMPONENT_TYPES, type Component, DEFAULT_SCENARIO, DELIVERY_BUDGETS, DELIVERY_VALUES, type DeckPlan, DeckPlanSchema, type Delivery, type DeliveryBudget, MODE_DEFINITIONS, MODE_VALUES, type Meta, type Mode, type ModeDefinition, PLAN_PAGE_COUNT_RANGE, type PageContent, type PlanPage, PlanPageSchema, type PlanPageType, type PlanRhythm, type PlanValidateResult, type PlanValidationIssue, PptfastError, type PptxIR, PptxIRSchema, SCENARIO_PRESETS, type ScenarioAxes, type ScenarioPreset, type Slide, type StyleOverride, StyleOverrideSchema, type ThemeDefinition, type ThemeInfo, type ThemeRegistration, ThemeSchema, VERSION, type ValidateResult, type ValidationIssue, assembleDeck, auditDeck, disassembleDeck, formatIssues, formatPlanIssues, generatePptx, getInstalledThemeIds, getThemeDefinition, irJsonSchema, listThemes, planJsonSchema, registerTheme, renderSlideSvg, resolvePlanThemeId, resolveScenario, styleJsonSchema, validateIr, validatePlan };
2765
+ 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 };