@liustack/pptfast 0.3.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,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.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 {
@@ -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
@@ -1158,8 +1287,8 @@ interface ValidateResult {
1158
1287
  }
1159
1288
  /**
1160
1289
  * 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
1290
+ * `narrative` (`resolveNarrative`, spec §5: an unrecognized preset name is a
1291
+ * `narrative`-path error, page-less) and run the content-quality gate
1163
1292
  * (`checkIrQuality`, passed the resolved axes) against the parsed IR. All
1164
1293
  * stages must pass for `ok: true`. Quality findings are reported the same
1165
1294
  * way as schema errors (page-scoped, 1-based). `checkIrQuality` itself tags
@@ -1171,18 +1300,35 @@ interface ValidateResult {
1171
1300
  * defeat the point of wiring this in: the spec's core principle is a hard
1172
1301
  * protocol gate, not hoped-for prompt compliance.
1173
1302
  *
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
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
1184
1311
  * `ValidateResult.normalized` on *every* return path below via
1185
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).
1186
1332
  */
1187
1333
  declare function validateIr(input: unknown): ValidateResult;
1188
1334
  /**
@@ -1215,6 +1361,494 @@ declare function irJsonSchema(): Record<string, unknown>;
1215
1361
  /** JSON Schema for style-token overrides (IR theme.style, --style files, config "style"). */
1216
1362
  declare function styleJsonSchema(): Record<string, unknown>;
1217
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
+
1218
1852
  type LayoutType = "cover" | "chapter" | "content" | "ending";
1219
1853
  interface StyleColors {
1220
1854
  bg: string;
@@ -1361,41 +1995,44 @@ declare function getInstalledThemeIds(): readonly string[];
1361
1995
  */
1362
1996
  declare function getThemeDefinition(id: string): ThemeDefinition;
1363
1997
 
1364
- declare const MODE_VALUES: readonly ["pyramid", "narrative", "instructional", "showcase", "briefing"];
1365
- declare const DELIVERY_VALUES: readonly ["text", "balanced", "presentation"];
1998
+ declare const STRATEGY_VALUES: readonly ["pyramid", "storytelling", "instructional", "showcase", "briefing"];
1999
+ declare const PACING_VALUES: readonly ["dense", "balanced", "spacious"];
1366
2000
  declare const AUDIENCE_VALUES: readonly ["executive", "technical", "customer", "public"];
1367
2001
 
1368
2002
  /**
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).
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).
1373
2009
  */
1374
- type Mode = (typeof MODE_VALUES)[number];
2010
+ type Strategy = (typeof STRATEGY_VALUES)[number];
1375
2011
  /**
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.
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.
1379
2015
  */
1380
- type Delivery = (typeof DELIVERY_VALUES)[number];
2016
+ type Pacing = (typeof PACING_VALUES)[number];
1381
2017
  /**
1382
2018
  * Tone anchor only (spec §5: audience is tone-anchoring only, no rendering
1383
2019
  * effect on the IR yet). Reserved for a future lint pass (e.g. executive ×
1384
2020
  * long paragraphs → suggest kpi_cards/verdict_banner instead). The rule set
1385
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 保持不变").
1386
2023
  */
1387
2024
  type Audience = (typeof AUDIENCE_VALUES)[number];
1388
- interface ScenarioAxes {
1389
- readonly mode: Mode;
1390
- readonly delivery: Delivery;
2025
+ interface NarrativeProfile {
2026
+ readonly strategy: Strategy;
2027
+ readonly pacing: Pacing;
1391
2028
  readonly audience: Audience;
1392
2029
  }
1393
2030
 
1394
- interface ModeDefinition {
1395
- id: Mode;
2031
+ interface StrategyDefinition {
2032
+ id: Strategy;
1396
2033
  /**
1397
2034
  * 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
2035
+ * from spec §5's strategy table for W4's weighted selection step (spec §6
1399
2036
  * step 4: in-set candidates get ×3 weight, out-of-set ×1 floor — not
1400
2037
  * implemented yet, this module only stores the data).
1401
2038
  *
@@ -1416,48 +2053,50 @@ interface ModeDefinition {
1416
2053
  * `svg/effective-layout.ts`, next to `resolveArchetypeId`, the sole
1417
2054
  * consumer). Deliberately a separate field from {@link tendencies} above,
1418
2055
  * 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
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
1421
2058
  * breaking change to an existing consumer. This field holds only
1422
2059
  * `LAYOUT_REGISTRY` content-archetype ids (`svg/layouts/registry.ts`'s
1423
2060
  * `CONTENT_LAYOUTS` keys) — cover/chapter/ending ids never appear in any
1424
- * mode's list here, which is exactly why `resolveArchetypeId`'s weighting
2061
+ * strategy's list here, which is exactly why `resolveArchetypeId`'s weighting
1425
2062
  * is a no-op for those three slide types (spec: "身份页个性来自 theme 不来自
1426
- * mode,均匀取样") without needing a slide-type special case — a weight
2063
+ * strategy,均匀取样") without needing a slide-type special case — a weight
1427
2064
  * lookup against an id that can never match falls through to the ×1 floor
1428
2065
  * 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).
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).
1432
2069
  */
1433
2070
  layoutTendencies: readonly string[];
1434
2071
  /**
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.
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.
1442
2079
  */
1443
- rhythmPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
2080
+ beatPolicy: "anchor-open" | "alternate" | "repetition-ok" | "anchor-sparse" | "uniform-dense";
1444
2081
  }
1445
- declare const MODE_DEFINITIONS: Record<Mode, ModeDefinition>;
2082
+ declare const STRATEGY_DEFINITIONS: Record<Strategy, StrategyDefinition>;
1446
2083
 
1447
- interface DeliveryBudget {
2084
+ interface PacingBudget {
1448
2085
  /**
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`
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`
1453
2090
  * once and passes it into `buildCtx`, which stores it as
1454
2091
  * `ComponentCtx.bodyFontPx` — the sole font-size input for the
1455
2092
  * paragraph/bullets/callout trio ("正文" = continuous running text).
1456
2093
  * Every other component's own bespoke type scale, the heading system, and
1457
2094
  * 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.
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).
1461
2100
  */
1462
2101
  bodyBaselinePx: number;
1463
2102
  /**
@@ -1476,23 +2115,24 @@ interface DeliveryBudget {
1476
2115
  * (`measureTextUnits`, CJK weight = 1.0) — a visual-width-weighted
1477
2116
  * character count, not a raw `.length`. The old physical ceiling (53,
1478
2117
  * derived from render geometry) was deleted in W3 without a replacement
1479
- * check because every delivery's editorial budget here (30/40/48) is
2118
+ * check because every pacing's editorial budget here (30/40/48) is
1480
2119
  * already strictly tighter than it — the quality gate now applies
1481
- * exactly this one number per delivery.
2120
+ * exactly this one number per pacing.
1482
2121
  */
1483
2122
  maxUnitsPerItem: number;
1484
2123
  };
1485
2124
  }
1486
2125
  /**
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.
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).
1490
2130
  */
1491
- declare const DELIVERY_BUDGETS: Record<Delivery, DeliveryBudget>;
2131
+ declare const PACING_BUDGETS: Record<Pacing, PacingBudget>;
1492
2132
 
1493
- interface ScenarioPreset {
2133
+ interface NarrativePreset {
1494
2134
  id: string;
1495
- axes: ScenarioAxes;
2135
+ axes: NarrativeProfile;
1496
2136
  /**
1497
2137
  * Soft theme recommendations — a suggestion, never a hard constraint
1498
2138
  * (spec §5). Surfaced in workflow step ① so an agent can open with a
@@ -1502,27 +2142,33 @@ interface ScenarioPreset {
1502
2142
  */
1503
2143
  themeRecommendations: readonly string[];
1504
2144
  }
1505
- /** Pinned to spec §5's 7 named presets and their theme recommendation table. */
1506
- declare const SCENARIO_PRESETS: Record<string, ScenarioPreset>;
1507
2145
  /**
1508
- * = `SCENARIO_PRESETS.general.axes` (briefing × balanced × public) the
1509
- * global default when scenario is omitted entirely (spec §5's defaults
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
1510
2156
  * chain).
1511
2157
  */
1512
- declare const DEFAULT_SCENARIO: ScenarioAxes;
2158
+ declare const DEFAULT_NARRATIVE: NarrativeProfile;
1513
2159
  /**
1514
- * Resolve a scenario input down to concrete axes, per spec §5's design
2160
+ * Resolve a narrative input down to concrete axes, per spec §5's design
1515
2161
  * principle "omission gets the default, a typo is a hard error" (weak-model
1516
2162
  * friendly: a model that leaves a field out gets a sane deck, a model that
1517
2163
  * misspells a value gets a loud, actionable error instead of a silently
1518
2164
  * wrong deck):
1519
2165
  *
1520
- * - `undefined` → {@link DEFAULT_SCENARIO} (the `general` preset's axes)
2166
+ * - `undefined` → {@link DEFAULT_NARRATIVE} (the `general` preset's axes)
1521
2167
  * - a preset id string → that preset's axes (unknown id throws
1522
2168
  * {@link PptfastError}, listing the available preset ids)
1523
2169
  * - 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
2170
+ * (strategy → "briefing", pacing → "balanced", audience → "public" — these
2171
+ * happen to equal `DEFAULT_NARRATIVE`'s values because `general` *is* that
1526
2172
  * exact combination, but the fallback here is per-axis, not "any omitted
1527
2173
  * axis falls back to the whole default object")
1528
2174
  *
@@ -1530,50 +2176,64 @@ declare const DEFAULT_SCENARIO: ScenarioAxes;
1530
2176
  * always throws {@link PptfastError} (never silently ignored or dropped) —
1531
2177
  * omission and a typo are different intents, and only the former has a
1532
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.
1533
2187
  */
1534
- declare function resolveScenario(input: string | Partial<ScenarioAxes> | undefined): ScenarioAxes;
2188
+ declare function resolveNarrative(input: string | Partial<NarrativeProfile> | undefined): NarrativeProfile;
1535
2189
 
1536
2190
  /**
1537
- * Deck plan schema + validation (spec §5 "plan artifact and hard gates", W5 task 2).
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).
1538
2196
  *
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).
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).
1547
2206
  *
1548
2207
  * Design mirrors `api.ts`'s `validateIr`/`ValidateResult` throughout
1549
2208
  * (structural zod pass first, then a sequential chain of hard-gate
1550
2209
  * 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
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
1553
2212
  * positional slide index.
1554
2213
  */
1555
2214
 
1556
2215
  /**
1557
2216
  * Mirrors `SlideSchema.type` / `SlideType` (`ir/index.ts`,
1558
2217
  * `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
2218
+ * here (not imported from either) — a page spec's `type` is a 4-value enum
1560
2219
  * on its own schema, not a re-export of IR's — but the `satisfies` clause
1561
2220
  * makes any future drift between the two a compile error instead of a
1562
2221
  * silent mismatch.
1563
2222
  */
1564
2223
  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];
2224
+ declare const BEAT_VALUES: readonly ["anchor", "dense", "breathing"];
2225
+ type PageSpecType = (typeof PAGE_TYPES)[number];
2226
+ type PageBeat = (typeof BEAT_VALUES)[number];
1568
2227
  /**
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").
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").
1575
2235
  */
1576
- declare const PlanPageSchema: z.ZodObject<{
2236
+ declare const PageSpecSchema: z.ZodObject<{
1577
2237
  id: z.ZodString;
1578
2238
  type: z.ZodEnum<{
1579
2239
  cover: "cover";
@@ -1582,7 +2242,7 @@ declare const PlanPageSchema: z.ZodObject<{
1582
2242
  ending: "ending";
1583
2243
  }>;
1584
2244
  heading: z.ZodString;
1585
- rhythm: z.ZodOptional<z.ZodEnum<{
2245
+ beat: z.ZodOptional<z.ZodEnum<{
1586
2246
  anchor: "anchor";
1587
2247
  dense: "dense";
1588
2248
  breathing: "breathing";
@@ -1590,19 +2250,27 @@ declare const PlanPageSchema: z.ZodObject<{
1590
2250
  focus: z.ZodOptional<z.ZodString>;
1591
2251
  summary: z.ZodOptional<z.ZodString>;
1592
2252
  }, z.core.$strict>;
1593
- type PlanPage = z.infer<typeof PlanPageSchema>;
2253
+ type PageSpec = z.infer<typeof PageSpecSchema>;
1594
2254
  /**
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.
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.
1602
2270
  */
1603
- declare const DeckPlanSchema: z.ZodObject<{
2271
+ declare const DeckSpecSchema: z.ZodObject<{
1604
2272
  version: z.ZodDefault<z.ZodLiteral<"1">>;
1605
- scenario: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
2273
+ narrative: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
1606
2274
  theme: z.ZodOptional<z.ZodString>;
1607
2275
  filename: z.ZodOptional<z.ZodString>;
1608
2276
  seed: z.ZodOptional<z.ZodNumber>;
@@ -1659,7 +2327,7 @@ declare const DeckPlanSchema: z.ZodObject<{
1659
2327
  ending: "ending";
1660
2328
  }>;
1661
2329
  heading: z.ZodString;
1662
- rhythm: z.ZodOptional<z.ZodEnum<{
2330
+ beat: z.ZodOptional<z.ZodEnum<{
1663
2331
  anchor: "anchor";
1664
2332
  dense: "dense";
1665
2333
  breathing: "breathing";
@@ -1668,10 +2336,10 @@ declare const DeckPlanSchema: z.ZodObject<{
1668
2336
  summary: z.ZodOptional<z.ZodString>;
1669
2337
  }, z.core.$strict>>;
1670
2338
  }, 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 {
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 {
1675
2343
  path: string;
1676
2344
  message: string;
1677
2345
  /** The offending page's `id`, when the issue is scoped to one specific
@@ -1680,53 +2348,57 @@ interface PlanValidationIssue {
1680
2348
  * whose own `id` itself failed to parse. */
1681
2349
  pageId?: string;
1682
2350
  }
1683
- interface PlanValidateResult {
2351
+ interface SpecValidateResult {
1684
2352
  ok: boolean;
1685
- plan?: DeckPlan;
1686
- errors: PlanValidationIssue[];
2353
+ spec?: DeckSpec;
2354
+ errors: SpecValidationIssue[];
1687
2355
  }
1688
- declare function formatPlanIssues(errors: PlanValidationIssue[]): string;
2356
+ declare function formatSpecIssues(errors: SpecValidationIssue[]): string;
1689
2357
  /**
1690
- * Plan-level theme default (spec §5's defaults chain: "theme omitted →
2358
+ * Deck-spec-level theme default (spec §5's defaults chain: "theme omitted →
1691
2359
  * consulting") — the same default IR's own `theme.id` field carries
1692
2360
  * (`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
2361
+ * validated {@link DeckSpec} (the CLI's OK-summary line) doesn't re-derive
1694
2362
  * the fallback itself.
1695
2363
  */
1696
- declare function resolvePlanThemeId(plan: DeckPlan): string;
2364
+ declare function resolveSpecThemeId(spec: DeckSpec): string;
1697
2365
  /**
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
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
1702
2370
  * 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").
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.
1704
2376
  */
1705
- declare const PLAN_PAGE_COUNT_RANGE: Record<Delivery, {
2377
+ declare const SPEC_PAGE_COUNT_RANGE: Record<Pacing, {
1706
2378
  min: number;
1707
2379
  max: number;
1708
2380
  }>;
1709
2381
  /**
1710
- * Validate raw JSON against the plan schema, then — once it parses — run the
2382
+ * Validate raw JSON against the spec schema, then — once it parses — run the
1711
2383
  * spec §5 hard-gate chain. Mirrors `validateIr`'s (`api.ts`) overall shape: a
1712
2384
  * structural zod pass first, then a sequence of isolated hard-gate
1713
2385
  * categories, each short-circuiting the whole chain on its own failure
1714
2386
  * (rather than accumulating errors across categories) so a later category
1715
2387
  * 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
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
1720
2392
  * should be authored as bare IR instead, not warned-and-shipped).
1721
2393
  */
1722
- declare function validatePlan(input: unknown): PlanValidateResult;
2394
+ declare function validateSpec(input: unknown): SpecValidateResult;
1723
2395
 
1724
2396
  /**
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
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
1727
2399
  * {@link assembleDeck}'s locked-field gate) and `subheading`/`decor`
1728
2400
  * (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
2401
+ * own layout — "pages/<id>.json contains only components" — a spec/pages deck can't
1730
2402
  * author either one — a hand-authored bare IR still can). Every field here is
1731
2403
  * a same-name, same-shape subset of `Slide`'s own optional fields
1732
2404
  * (`../ir`'s `SlideSchema`) — reused, not redeclared, so the two can't drift.
@@ -1738,15 +2410,16 @@ interface PageContent {
1738
2410
  background?: BackgroundSpec;
1739
2411
  image_side?: "left" | "right";
1740
2412
  footnote?: string;
2413
+ notes?: string;
1741
2414
  }
1742
2415
  interface AssembleResult {
1743
2416
  ir: PptxIR;
1744
2417
  /**
1745
- * Set only when `plan.seed` was absent and {@link assembleDeck} generated
2418
+ * Set only when `spec.seed` was absent and {@link assembleDeck} generated
1746
2419
  * one deterministically (see the seed section of this function's doc
1747
- * comment) — `undefined` when `plan.seed` was already present and simply
2420
+ * comment) — `undefined` when `spec.seed` was already present and simply
1748
2421
  * 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
2422
+ * suggest writing the value back into the spec file, never rewrite it
1750
2423
  * itself (assemble stays a pure function, no fs side effects here).
1751
2424
  */
1752
2425
  generatedSeed?: number;
@@ -1763,20 +2436,20 @@ interface AssembleResult {
1763
2436
  materializedLayoutCount?: number;
1764
2437
  }
1765
2438
  /**
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.
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.
1772
2445
  *
1773
- * 1. `plan` is `unknown` (same boundary `validatePlan` itself has — a plan
2446
+ * 1. `spec` is `unknown` (same boundary `validateSpec` itself has — a spec
1774
2447
  * is almost always freshly `JSON.parse`d off disk by the caller) —
1775
2448
  * invalid shape or a failed hard gate throws {@link PptfastError} with
1776
- * `validatePlan`'s own formatted issue list, not a re-derived message.
2449
+ * `validateSpec`'s own formatted issue list, not a re-derived message.
1777
2450
  * 2. Shape guard + locked-field protection: a `pages[id]` entry must first be
1778
2451
  * 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
2452
+ * `pages` is `unknown`-shaped off disk same as `spec` itself (step 1), so
1780
2453
  * a JSON `null`/string/array content value is a real possibility, not
1781
2454
  * just a type-system hole, and `Object.hasOwn` throws its own
1782
2455
  * uninformative native `TypeError` on `null` (and silently no-ops on a
@@ -1790,63 +2463,69 @@ interface AssembleResult {
1790
2463
  * `"heading": null`-turned-`undefined` or a copy-pasted empty key is
1791
2464
  * exactly the drift this gate exists to catch instead of silently
1792
2465
  * ignoring.
1793
- * 3. Orphan keys: a `pages` entry whose id isn't any plan page's id. Listed
2466
+ * 3. Orphan keys: a `pages` entry whose id isn't any spec page's id. Listed
1794
2467
  * together with a fix suggestion, checked only after every present page
1795
2468
  * has cleared the locked-field gate (step 2) — an orphan file that
1796
2469
  * *also* happens to redeclare `heading` reports as locked-field first.
1797
- * 4. Missing pages (a plan id with no `pages` entry) become a placeholder
2470
+ * 4. Missing pages (a spec id with no `pages` entry) become a placeholder
1798
2471
  * slide — never an error. Spec §7's own words: "assemble's precise
1799
2472
  * semantics — a missing page always succeeds (placeholder), a structural
1800
- * contradiction (orphan file / bad plan / id conflict) errors". A
2473
+ * contradiction (orphan file / bad spec / id conflict) errors". A
1801
2474
  * declared `summary` becomes the placeholder's `subheading` (the one spot
1802
- * `summary` — otherwise a plan-only anchor, see step 5 — does reach the
2475
+ * `summary` — otherwise a spec-only anchor, see step 5 — does reach the
1803
2476
  * IR) so a `--draft` preview of an unfilled deck still reads as more than
1804
2477
  * 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
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
1810
2483
  * 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
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
1814
2493
  * present. When absent, this function omits the field from the raw
1815
2494
  * object it hands to {@link PptxIRSchema} rather than re-deriving IR's
1816
2495
  * own default value a second time — one default source of truth, the
1817
2496
  * schema itself (e.g. `theme` omitted here becomes `{ id: "consulting" }`,
1818
2497
  * exactly like a bare hand-authored IR that never mentions theme at all —
1819
2498
  * not a value this function needs to know).
1820
- * 7. Seed: `plan.seed` present → passed through, `generatedSeed` stays
2499
+ * 7. Seed: `spec.seed` present → passed through, `generatedSeed` stays
1821
2500
  * `undefined` on the result. Absent → {@link generateSeed} derives one
1822
- * from `filename` + the plan's own ordered page-id list (never page
2501
+ * from `filename` + the spec's own ordered page-id list (never page
1823
2502
  * *content* — see that function's own doc comment for why), written to
1824
2503
  * `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
2504
+ * writing it back into the spec file (this function never touches disk
1826
2505
  * itself).
1827
2506
  * 8. Idempotence: every step above is a pure function of its inputs (no
1828
2507
  * randomness, no wall-clock, no reliance on unordered iteration) — two
1829
- * calls with structurally-equal `plan`/`pages` produce deep-equal
2508
+ * calls with structurally-equal `spec`/`pages` produce deep-equal
1830
2509
  * results, `generatedSeed` included. Exercised directly by this module's
1831
2510
  * test suite rather than asserted here.
1832
2511
  */
1833
- declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>): AssembleResult;
2512
+ declare function assembleDeck(spec: unknown, pages: Record<string, PageContent>): AssembleResult;
1834
2513
  /**
1835
- * Inverse of {@link assembleDeck}: reconstructs `{ plan, pages }` from an
2514
+ * Inverse of {@link assembleDeck}: reconstructs `{ spec, pages }` from an
1836
2515
  * existing IR well enough that `assembleDeck(...disassembleDeck(ir))`
1837
2516
  * reproduces every slide's content — but the map is not lossless in both
1838
2517
  * directions, only in the direction the round trip actually exercises
1839
- * (IR → plan/pages → IR). Fields with no IR-side home never survive being
2518
+ * (IR → spec/pages → IR). Fields with no IR-side home never survive being
1840
2519
  * written *to* the IR in the first place, so there is nothing here to read
1841
2520
  * back:
1842
2521
  *
1843
- * - `rhythm` and `focus` never appear on any produced {@link PlanPage} —
1844
- * both are plan-only authoring anchors with no corresponding `Slide`
2522
+ * - `beat` and `focus` never appear on any produced {@link PageSpec} —
2523
+ * both are spec-only authoring anchors with no corresponding `Slide`
1845
2524
  * field at all (see {@link assembleDeck} step 5's doc comment). Nothing
1846
2525
  * here could recover a value that was never written anywhere.
1847
2526
  * - `summary` is recovered *only* for a placeholder slide, by reversing
1848
2527
  * step 4's `summary` → `subheading` injection (`slide.subheading` back to
1849
- * `planPage.summary`). A non-placeholder slide's own `subheading` — a
2528
+ * `pageSpec.summary`). A non-placeholder slide's own `subheading` — a
1850
2529
  * legitimate, independent `Slide` field a hand-authored bare IR is free to
1851
2530
  * set — has no {@link PageContent} field to land in (spec §7's pages/
1852
2531
  * record is deliberately narrower than `Slide` itself, see that
@@ -1855,14 +2534,14 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1855
2534
  * a filled page) — so it is dropped. Same for `decor`: a real `Slide`
1856
2535
  * field, absent from `PageContent`'s shape entirely.
1857
2536
  * - `theme.style` / `theme.brand` overrides collapse to a bare theme-id
1858
- * string (`DeckPlanSchema.theme` has no shape for either) — only `theme.id`
2537
+ * string (`DeckSpecSchema.theme` has no shape for either) — only `theme.id`
1859
2538
  * survives. That `theme.brand` is `ThemeSchema.brand` (`BrandConfigSchema`
1860
2539
  * — `suppressFooterOnCardContent`/`suppressFooterRule`, footer-chrome
1861
2540
  * flags owned by the *theme*) — not to be confused with the deck-level
1862
2541
  * `brand` field below, a different, unrelated schema despite the shared
1863
2542
  * name.
1864
2543
  * - `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
2544
+ * `{ spec, pages }` has no `assets` field, and this module stays zero-fs
1866
2545
  * by design (this file's own top comment), so it has no way to write an
1867
2546
  * `assets/` directory itself. Any `asset_id` reference inside a copied
1868
2547
  * `components`/`background` survives untouched ({@link extractPageContent}
@@ -1881,10 +2560,10 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1881
2560
  * collision: the top-level `brand` field (`BrandSchema` — `logo_asset_id` /
1882
2561
  * `position`, the deck logo/position `BrandChrome` reads,
1883
2562
  * `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.
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.
1888
2567
  *
1889
2568
  * `layout` deserves a different kind of callout: it round-trips as plain
1890
2569
  * content like any other field ({@link extractPageContent} copies
@@ -1913,7 +2592,7 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1913
2592
  * {@link UNTITLED_HEADING} for a missing/blank `heading`, and a positional
1914
2593
  * `p-<1-based-ordinal>-<type>` scheme for a missing `id` (stable across
1915
2594
  * 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
2595
+ * and type — but, unlike a spec-assigned id, *not* stable across inserting
1917
2596
  * or reordering slides — out of scope here, since a bare IR with no `id` at
1918
2597
  * all has no stabler identity to fall back to in the first place).
1919
2598
  *
@@ -1932,10 +2611,44 @@ declare function assembleDeck(plan: unknown, pages: Record<string, PageContent>)
1932
2611
  * function) is what actually closes the vulnerability.
1933
2612
  */
1934
2613
  declare function disassembleDeck(ir: PptxIR): {
1935
- plan: DeckPlan;
2614
+ spec: DeckSpec;
1936
2615
  pages: Record<string, PageContent>;
1937
2616
  };
1938
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
+
1939
2652
  /**
1940
2653
  * `pptfast audit` finding shape (v0.3 W6, spec §7 workflow ④). `page` is
1941
2654
  * 1-based (matches `ValidationIssue.page` in `api.ts`); `slideId` is set
@@ -1946,38 +2659,72 @@ declare function disassembleDeck(ir: PptxIR): {
1946
2659
  interface AuditFinding {
1947
2660
  page: number;
1948
2661
  slideId?: string;
1949
- code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap";
2662
+ code: "overflow" | "out-of-bounds" | "low-contrast" | "overlap" | "content-truncated" | "content-dropped";
1950
2663
  message: string;
1951
2664
  detail?: Record<string, unknown>;
1952
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
+ }
1953
2683
  interface AuditReport {
1954
2684
  findings: AuditFinding[];
1955
2685
  pagesAudited: number;
1956
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;
1957
2693
  }
1958
2694
  /**
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.
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.
1976
2702
  *
1977
2703
  * Placeholder pages (`slide.placeholder === true`) are skipped — assemble's
1978
2704
  * stand-in for content nobody has written yet has nothing to audit, same
1979
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).
1980
2722
  */
1981
- declare function auditDeck(ir: PptxIR): AuditReport;
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>;
1982
2729
 
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 };
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 };