@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/README.md CHANGED
@@ -63,25 +63,28 @@ const bytes = await generatePptx(ir) // Uint8Array, ready to write to a .pptx
63
63
  | Command | Does |
64
64
  |---|---|
65
65
  | `render <target> -o <out.pptx> [--theme <id>] [--style <file>] [--draft]` | Validate + render to a `.pptx` — `target` is an IR JSON file, a deck project directory, or a bare deck name (see Deck projects) |
66
- | `validate <target>` | Check the IR, print page-scoped errors — same `target` forms as `render` |
67
- | `audit <target> [--json]` | Deterministic geometry review (overflow/out-of-bounds/low-contrast/overlap) — same `target` forms as `render`, exits 1 when it finds anything (see Auditing) |
68
- | `plan validate <plan.json>` | Check a deck plan against the schema and mode-aware hard gates (see Deck projects) |
66
+ | `validate <target>` | Check the IR, print page-scoped errors and advisory warnings — same `target` forms as `render` |
67
+ | `audit <target> [--json] [--pixels]` | Deterministic geometry review (overflow/out-of-bounds/low-contrast/overlap/content-truncated/content-dropped) — same `target` forms as `render`, exits 1 when it finds anything (see Auditing) |
68
+ | `spec validate <spec.json>` | Check a deck spec against the schema and strategy-aware hard gates (see Deck projects) |
69
69
  | `assemble <dir\|name> [-o <file>]` | Materialize a deck project directory into a single IR JSON file |
70
70
  | `disassemble <ir.json> -o <dir>` | Split an IR JSON file into a deck project directory |
71
- | `schema [--style \| --plan]` | Print the IR JSON Schema (or the style-override schema, or the deck plan schema) |
71
+ | `schema [--style \| --spec]` | Print the IR JSON Schema (or the style-override schema, or the deck spec schema) |
72
72
  | `themes [--json]` | List the 13 built-in themes |
73
- | `scenarios [--json]` | List named scenario presets (mode/delivery/audience axes + theme recommendations) |
73
+ | `narratives [--json]` | List named narrative presets (strategy/pacing/audience axes + theme recommendations) |
74
74
  | `preview <target> -o <dir> [--html]` | Render each slide to a standalone SVG (`--html` also writes a self-contained `preview.html`) — same `target` forms as `render`, never gated on placeholder pages |
75
+ | `migrate <input> -o <output>` | Convert a v3 IR file to v4, or a `deck.plan.json` project directory to `deck.spec.json` — deterministic, no model call (see The IR and Deck projects) |
75
76
  | `init` | Scaffold `pptfast.config.json` |
76
77
  | `check-update` / `self-update` | Check npm for a newer release / update the global install |
77
78
 
78
79
  ## The IR
79
80
 
80
- Run `node dist/cli.js schema` for the full JSON Schema — feed it to a model before asking it to write IR. A deck (`PptxIR`) has `version` (currently `"3"`), `filename`, an optional `scenario` (a preset id string or a partial axes object — see Scenarios below), `theme` (`id` plus optional `style`/`brand` overrides), `meta`, and `assets` — all optional with sane defaults — plus a separate optional `brand` (logo placement) and a required ordered list of `slides`. Each slide has a `type` (`cover`, `chapter`, `content`, `ending`), an optional `layout` (an explicit page-layout id that always wins over auto-selection — omit it and pptfast auto-selects one, see Layout selection below), an optional `arrangement` (how a content slide's body is laid out, e.g. `two_column`, `kpi_focus`), and a list of typed `components` (`bullets`, `kpi_cards`, `image`, `chart`, …). `assets` is `{ images: { [id]: { src, alt? } } }` — components reference images by `asset_id`, so the same image can be reused across slides without duplication.
81
+ Run `node dist/cli.js schema` for the full JSON Schema — feed it to a model before asking it to write IR. A deck (`PptxIR`) has `version` (currently `"4"`, and now the default when omitted), `filename`, an optional `narrative` (a preset id string or a partial axes object — see Narratives below), `theme` (`id` plus optional `style`/`brand` overrides), `meta`, and `assets` — all optional with sane defaults — plus a separate optional `brand` (logo placement) and a required ordered list of `slides`. Each slide has a `type` (`cover`, `chapter`, `content`, `ending`), an optional `layout` (an explicit page-layout id that always wins over auto-selection — omit it and pptfast auto-selects one, see Layout selection below), an optional `arrangement` (how a content slide's body is laid out, e.g. `two_column`, `kpi_focus`), and a list of typed `components` (`bullets`, `kpi_cards`, `image`, `chart`, …). `assets` is `{ images: { [id]: { src, alt? } } }` — components reference images by `asset_id`, so the same image can be reused across slides without duplication.
81
82
 
82
- A deck also carries an optional `seed` (an integer that keeps auto-selected layouts stable across revisions — see Layout selection below for how it's derived when omitted). Any slide may set a stable `id` (what plan pages and validation error messages reference it by) and `placeholder: true` (a slide with no content yet — injected by `assemble` for a plan page nobody has filled in, skipped by the content-quality checks, and blocking `render` unless `--draft`). Field names that commonly drift between a model's output and the schema (25 synonym pairs across component types, e.g. kpi `title`→`label`, quote `content`→`text`) are silently normalized to the canonical name at validate time — `validate`/`render`/`preview` print a note listing what changed, never a hard error.
83
+ A deck also carries an optional `seed` (an integer that keeps auto-selected layouts stable across revisions — see Layout selection below for how it's derived when omitted). Any slide may set a stable `id` (what spec pages and validation error messages reference it by), `placeholder: true` (a slide with no content yet — injected by `assemble` for a spec page nobody has filled in, skipped by the content-quality checks, and blocking `render` unless `--draft`), and an optional `notes` (aliases `note`/`speaker_notes`/`speakerNotes`) that exports as a native PowerPoint speaker note — content for the presenter's own view, never drawn onto the slide canvas and never counted toward any layout capacity. Field names that commonly drift between a model's output and the schema (40 synonym pairs across component types, e.g. kpi `title`→`label`, quote `content`→`text`, swot `strength`→`strengths`, bmc `partners`→`key_partners`) are silently normalized to the canonical name at validate time — `validate`/`render`/`preview` print a note listing what changed, never a hard error. That rescue is scoped to weak-model synonym drift only — it does not cover pre-v4 vocabulary. A v4-labeled document that writes `scenario` instead of `narrative`, `mode`/`delivery` instead of `strategy`/`pacing`, or the old `narrative`/`text`/`presentation` axis values hard-rejects, listing the current names/values, exactly like any other unrecognized field or value. An explicit `version: "3"` (or `"2"`) also hard-rejects, with a migration pointer — see `pptfast migrate` below, the only supported path for old-vocabulary input.
83
84
 
84
- The v3 IR schema is frozen as of 0.3.0 future evolution is additive only (new optional fields, new enum members), and any breaking change ships under a new top-level `version` value with the same hard-reject-and-migration treatment v2 got.
85
+ Four component types are *full-body*: `swot` (strengths/weaknesses/opportunities/threats), `bmc` (the nine-block Business Model Canvas), `waterfall` (a running-total bridge chart), and `gantt` (dated bars on a shared numeric axis). Each fills a slide's entire content rect and must be the only component on its slide mixing one in with anything else fails `validate` instead of silently dropping the sibling.
86
+
87
+ The v4 IR schema is frozen as of 0.4.0 — future evolution is additive only (new optional fields, new enum members), and any breaking change ships under a new top-level `version` value with the same hard-reject-and-migration treatment v3 got. `pptfast migrate <v3-file.json> -o <out.json>` deterministically converts a v3 file to v4 (field renames only — same theme, layout selection, content budgets, and visual output) — see Deck projects below for the sibling `deck.plan.json` → `deck.spec.json` conversion.
85
88
 
86
89
  ## Themes
87
90
 
@@ -103,30 +106,30 @@ A theme bundles a style (design tokens), a brand (identity chrome), and a layout
103
106
  | `luxe` | Luxe |
104
107
  | `heritage` | Heritage |
105
108
 
106
- ## Scenarios
109
+ ## Narratives
107
110
 
108
- A scenario is three narrative axes, independent of theme (visual style), that set editorial discipline: `mode` (how the argument is built — `pyramid`, `narrative`, `instructional`, `showcase`, `briefing`), `delivery` (how dense the content is — `text`, `balanced`, `presentation`), and `audience` (a tone anchor — `executive`, `technical`, `customer`, `public`, no rendering effect yet). Set the IR's top-level `scenario` to a named preset string (e.g. `"boardroom-report"`) or a partial axes object (e.g. `{ "delivery": "presentation" }`) — an omitted axis, or an omitted `scenario` field entirely, falls back to `general` (`briefing` × `balanced` × `public`). An unknown preset name or axis value is a hard validate error listing what's available.
111
+ A narrative is three axes, independent of theme (visual style), that set editorial discipline: `strategy` (how the argument is built — `pyramid`, `storytelling`, `instructional`, `showcase`, `briefing`), `pacing` (how dense the content is — `dense`, `balanced`, `spacious`), and `audience` (a tone anchor — `executive`, `technical`, `customer`, `public`, no rendering effect yet). Set the IR's top-level `narrative` to a named preset string (e.g. `"boardroom-report"`) or a partial axes object (e.g. `{ "pacing": "spacious" }`) — an omitted axis, or an omitted `narrative` field entirely, falls back to `general` (`briefing` × `balanced` × `public`). An unknown preset name or axis value is a hard validate error listing what's available.
109
112
 
110
- `delivery` drives the content-quality gate and the body-text baseline (paragraph/bullets/callout only — every other component's own type scale and the heading system are unaffected): the per-slide component budget and the bullets budget (item count and per-item length) both tighten from `text` toward `presentation`, while the body font size grows the other way — density is additionally capped by whichever layout the slide resolves to, whichever ceiling is tighter.
113
+ `pacing` drives the content-quality gate and the body-text baseline (paragraph/bullets/callout only — every other component's own type scale and the heading system are unaffected): the per-slide component budget and the bullets budget (item count and per-item length) both tighten from `dense` toward `spacious`, while the body font size grows the other way — density is additionally capped by whichever layout the slide resolves to, whichever ceiling is tighter. These are editorial guidance, not hard limits: `validate` reports them as warnings and still succeeds — only genuine render-safety ceilings (below) can block generation.
111
114
 
112
- | delivery | body text | components / slide | bullets |
115
+ | pacing | body text | components / slide | bullets |
113
116
  |---|---|---|---|
114
- | `text` | 20px | 5 | up to 6 items, ~48 characters each |
117
+ | `dense` | 20px | 5 | up to 6 items, ~48 characters each |
115
118
  | `balanced` (the default) | 24px | 4 | up to 5 items, ~40 characters each |
116
- | `presentation` | 32px | 3 | up to 4 items, ~30 characters each |
119
+ | `spacious` | 32px | 3 | up to 4 items, ~30 characters each |
117
120
 
118
- Bullets shrink below their tier's baseline to fit when needed, down to a 14px floor, before any overflow handling kicks in. `pptfast validate` reports the exact numbers that applied to each slide.
121
+ Bullets shrink below their tier's baseline to fit when needed, down to a 14px floor, before any overflow handling kicks in. Across every bullet style — `default`, `plain`, `divided`, `numbered`, and `checklist` alike — an item long enough to still overflow at that floor is a hard validate error, distinct from, and looser than, the per-pacing length guidance above, since it genuinely loses text to an ellipsis at render, not just reads as verbose. `pptfast validate` reports the exact numbers that applied to each slide.
119
122
 
120
- Run `pptfast scenarios [--json]` to list the named presets (each carries soft theme recommendations — a starting suggestion, never a constraint) plus the raw axes tables.
123
+ Run `pptfast narratives [--json]` to list the named presets (each carries soft theme recommendations — a starting suggestion, never a constraint) plus the raw axes tables.
121
124
 
122
125
  ## Layout selection
123
126
 
124
- When a slide omits `layout`, pptfast resolves one automatically in four deterministic steps: the page type's full registry pool → the theme's layout set for that page type (full by default, see Themes above) → the scenario's `mode` softly upweights (×3) a handful of content-layout ids that suit that mode, everything else stays at a ×1 floor (cover/chapter/ending are never weighted — their character comes from the theme, not the mode) → a seeded weighted pick, swapped deterministically to the runner-up when it would repeat the immediately preceding slide's layout. An explicit `layout` always wins and skips every step above. Whether the content actually fits is enforced separately by `validate`'s density gate, never by selection — so editing a page's content cannot silently flip its layout.
127
+ When a slide omits `layout`, pptfast resolves one automatically in four deterministic steps: the page type's full registry pool → the theme's layout set for that page type (full by default, see Themes above) → the narrative's `strategy` softly upweights (×3) a handful of content-layout ids that suit that strategy, everything else stays at a ×1 floor (cover/chapter/ending are never weighted — their character comes from the theme, not the strategy) → a seeded weighted pick, swapped deterministically to the runner-up when it would repeat the immediately preceding slide's layout. An explicit `layout` always wins and skips every step above. Whether the content fits is flagged separately by `validate`'s density gate (editorial guidance, not a hard block — see Narratives above), never by selection — so editing a page's content cannot silently flip its layout.
125
128
 
126
129
  The pick is fully deterministic — the same IR always resolves the same way, so preview and the final render never disagree. Staying stable *across revisions* (editing one page without reshuffling every other page's auto-picked layout) additionally needs a persisted `seed`, resolved in this order:
127
130
 
128
131
  1. An explicit `ir.seed` — full revision stability, always wins.
129
- 2. A deck project's own seed: `pptfast assemble` derives one from the plan's filename and page ids the first time a plan omits `seed`, and prints a note with the value — copy it into `deck.plan.json`'s `seed` field to persist it.
132
+ 2. A deck project's own seed: `pptfast assemble` derives one from the spec's filename and page ids the first time a spec omits `seed`, and prints a note with the value — copy it into `deck.spec.json`'s `seed` field to persist it.
130
133
  3. Neither set: a content hash of `filename` + every slide's `heading` (legacy-compatible) — editing any heading reshuffles every auto-picked layout deck-wide.
131
134
 
132
135
  `pptfast assemble` also writes every auto-picked `layout` back into the assembled `deck.json` (a page file's own explicit `layout` is left untouched) — the CLI notes how many pages it filled in.
@@ -147,24 +150,26 @@ same override in `theme.style` for fully self-contained decks.
147
150
 
148
151
  ## Deck projects
149
152
 
150
- A deck can be authored two ways, and every command that takes IR accepts either: a single **IR JSON file** (everything above), or a **deck project directory** — the same content split across files so an agent can plan a deck's structure first, then write and revise it page by page instead of holding one growing JSON blob in context.
153
+ A deck can be authored two ways, and every command that takes IR accepts either: a single **IR JSON file** (everything above), or a **deck project directory** — the same content split across files so an agent can spec out a deck's structure first, then write and revise it page by page instead of holding one growing JSON blob in context.
151
154
 
152
155
  ```
153
156
  my-deck/
154
- deck.plan.json the locked plan: page order, type, and heading for every page
155
- pages/<page-id>.json one file per filled page (components/layout/arrangement/background/image_side/footnote)
157
+ deck.spec.json the locked spec: page order, type, and heading for every page
158
+ pages/<page-id>.json one file per filled page (components/layout/arrangement/background/image_side/footnote)
156
159
  assets/ local images, auto-registered by filename (image id = filename without extension)
157
160
  ```
158
161
 
159
- `deck.plan.json` validates on its own, before any page exists: `pptfast plan validate deck.plan.json` checks the schema plus mode-aware hard gates (boundary pages, heading length, rhythm rotation, page count vs. delivery). A plan page with no matching `pages/<id>.json` becomes a **placeholder** slide — heading only, not missing — so a partially-written deck always assembles and previews. `pptfast render` refuses to export a deck with unfilled placeholders unless you pass `--draft`. `pptfast preview` never gates on them.
162
+ `deck.spec.json` validates on its own, before any page exists: `pptfast spec validate deck.spec.json` checks the schema plus strategy-aware hard gates (boundary pages, heading length, beat rotation, page count vs. pacing). A spec page with no matching `pages/<id>.json` becomes a **placeholder** slide — heading only, not missing — so a partially-written deck always assembles and previews. `pptfast render` refuses to export a deck with unfilled placeholders unless you pass `--draft`. `pptfast preview` never gates on them. A directory still carrying the pre-v4 `deck.plan.json` instead of `deck.spec.json` is not read directly — `pptfast migrate <dir> -o <dir>` converts it in place (writes `deck.spec.json` alongside, never overwrites, never deletes the source — delete `deck.plan.json` yourself once you've confirmed the new file); a directory with both files present is a hard error, never a guessed priority.
160
163
 
161
- `pptfast assemble <dir>` materializes plan + pages + assets into a single IR JSON file (`deck.json` by default). `pptfast disassemble <ir.json> -o <dir>` does the reverse (documented-lossy — plan-only fields like `rhythm`/`focus` have no IR-side home to recover). `render`/`validate`/`preview` accept a directory directly too, assembling in memory first.
164
+ `pptfast assemble <dir>` materializes spec + pages + assets into a single IR JSON file (`deck.json` by default). `pptfast disassemble <ir.json> -o <dir>` does the reverse (documented-lossy — spec-only fields like `beat`/`focus` have no IR-side home to recover). `render`/`validate`/`preview` accept a directory directly too, assembling in memory first.
162
165
 
163
166
  Deck project directories can be referenced by a bare name instead of a path — `pptfast render my-deck -o out.pptx` resolves `my-deck` under `$PPTFAST_HOME/decks` (`$PPTFAST_HOME` defaults to `~/.pptfast`) when no local file or directory of that name exists. All deck defaults resolve in four layers, highest wins: CLI flag > project `pptfast.config.json` > user `~/.pptfast/config.json` > the deck's own values. Both config layers can set `decksDir` to redirect where bare names resolve — the project layer's value resolves against that config file's own directory (for a team that wants deck projects checked into the repo), the user layer's against `$PPTFAST_HOME`. Project wins when both are set.
164
167
 
165
168
  ## Auditing
166
169
 
167
- `pptfast audit <target> [--json]` renders every page off-screen and runs a deterministic geometry review — no LLM screenshot squinting, no variance. Four checks: **overflow** (text past its own box or column), **out-of-bounds** (past the page edge), **low-contrast** (WCAG relative-luminance ratio between text and its resolved background), and **overlap** (two components' regions substantially colliding). Advisory, not a hard gate — `validate` already rejects structurally invalid or over-dense decks. Audit catches what a valid deck can still get wrong at render time (an author-chosen near-background text color, two components whose combined content collides).
170
+ `pptfast audit <target> [--json]` renders every page off-screen and runs a deterministic geometry review — no LLM screenshot squinting, no variance. Six checks: **overflow** (text past its own box or column), **out-of-bounds** (past the page edge), **low-contrast** (WCAG relative-luminance ratio between text and its resolved background), **overlap** (two components' regions substantially colliding), **content-truncated** (text the renderer had to cut short with an ellipsis to fit), and **content-dropped** (a "+N more" marker — a card list or a whole component that didn't fit and got hidden). Advisory, not a hard gate — `validate` already rejects structurally invalid or over-dense decks. Audit catches what a valid deck can still get wrong at render time (an author-chosen near-background text color, two components whose combined content collides, a card list that had to drop an item to fit).
171
+
172
+ Add `--pixels` (Node only, needs the optional `sharp` dependency) to also catch the one thing the SVG-only checks can't see — text sitting directly on an unscrimmed photo background — by rasterizing the page and sampling real pixels. Every response now also carries a `checks` field (`{ svg: "completed", pixels: "not-requested" | "completed" }`) so a caller can always tell what actually ran instead of reading a missing check as a pass (see `docs/contrast-system.md` for the pixel layer's own determinism caveat).
168
173
 
169
174
  Run it once every page is filled, on the same `target` forms as `validate`/`render` (file, deck project directory, or bare name). Human output groups findings by page (`page 3 (p-kpi): [low-contrast] …`, each message carries a fix suggestion) plus a summary line. `--json` prints the full machine-readable report. The exit code alone is agent-judgeable: `0` clean, `1` when it finds anything — fix the flagged page and re-run `audit` alone, no need to re-render. Skipped placeholder pages are noted, the same "not missing, just not written yet" treatment used everywhere else.
170
175
 
@@ -175,17 +180,18 @@ pptfast audit examples/basic.json
175
180
 
176
181
  ## For AI agents
177
182
 
178
- The recommended loop for an agent generating a deck: read `pptfast schema` to learn the vocabulary, write an IR JSON, run `pptfast validate` and fix whatever it reports (errors carry a page number and a fixable-in-place message — the point is to close this loop without a human), then `pptfast audit` for the same kind of fixable-in-place feedback on what a *valid* deck can still get wrong at render time (overflow, low-contrast, overlap — exit code alone says whether it's clean), then `pptfast render`. `pptfast preview` gives the agent SVG files it can look at to self-check layout before committing to a render. Add `--html` to also write a self-contained `preview.html` for a human to review (keyboard nav, placeholder badges — a remote-URL image asset stays remote, the one self-containment gap). The Claude Code plugin above wraps this loop as a skill ([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md)).
183
+ The recommended loop for an agent generating a deck: read `pptfast schema` to learn the vocabulary, write an IR JSON, run `pptfast validate` and fix whatever it reports (errors carry a page number and a fixable-in-place message — the point is to close this loop without a human), then `pptfast audit` for the same kind of fixable-in-place feedback on what a *valid* deck can still get wrong at render time (overflow, low-contrast, overlap — exit code alone says whether it's clean), then `pptfast render`. `pptfast preview` gives the agent SVG files it can look at to self-check layout before committing to a render. Add `--html` to also write a self-contained `preview.html` for a human to review (keyboard nav, placeholder badges — a remote-URL image asset stays remote, the one self-containment gap). When every page is filled, that `preview.html` also overlays the same `audit` findings (per-page badges plus a findings panel, click to jump to the page) so a human reviewer sees them without a terminal — a deck with any placeholder page shows a one-line "audit skipped" notice instead. The reviewer can leave free-text per-page annotations right in `preview.html` and export them as a `revision-request.json` (a Blob download, no network or file write — preview stays read-only) for the agent to route back through `pages/*.json`. The Claude Code plugin above wraps this loop as a skill ([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md)). This exact loop is exercised by an internal, model-agnostic benchmark (`tests/bench/`, not published to npm) that mechanically scores how well a model follows the skill on a fixed question bank — see `tests/bench/README.md`.
179
184
 
180
185
  ## Roadmap
181
186
 
182
187
  - **v0.2** — Claude Code plugin + skill wrapping the render loop (shipped), design token overrides (`--style`), `init`/self-update commands.
183
- - **v0.3** — scenario-driven narrative axes (mode/delivery/audience), an explicit layout + component registry with weighted seeded selection, the deck plan/assemble workflow, a deterministic geometry audit, a self-contained HTML preview, and the six-phase skill (shipped, schema frozen see The IR above).
184
- - **v0.4** — theme ecosystem (distributable theme registry, theme-customization skill, custom brand slots), richer motion (more entrance animations), Office real-device testing, web playground, 1.0.
188
+ - **v0.3** — narrative-driven axes (strategy/pacing/audience), an explicit layout + component registry with weighted seeded selection, the deck spec/assemble workflow, a deterministic geometry audit, a self-contained HTML preview, and the six-phase skill (shipped; these axes shipped named `scenario`/`mode`/`delivery`, renamed to `narrative`/`strategy`/`pacing` in the v0.4 vocabulary rewrite below).
189
+ - **v0.4** — vocabulary-v4: `scenario`→`narrative`, `mode`→`strategy`, `delivery`→`pacing`, `plan`→`spec` (`deck.plan.json`→`deck.spec.json`), page `rhythm`→`beat`, a deterministic `pptfast migrate` command, no behavior change (shipped, schema frozen as of 0.4.0 see The IR above).
190
+ - **v0.5+** — theme ecosystem (distributable theme registry, theme-customization skill, custom brand slots), richer motion (more entrance animations), Office real-device testing, web playground, 1.0.
185
191
 
186
192
  ## Credits
187
193
 
188
- Icon primitives are extracted from [lucide](https://lucide.dev) (ISC License). pptfast itself was extracted from a production AI-deck-generation system and CJK-typography-tuned (full-width punctuation width, Chinese line breaking, a Chinese-first font stack) from day one.
194
+ Icon primitives are extracted from [lucide](https://lucide.dev) (ISC License). pptfast itself was extracted from a production AI-deck-generation system and CJK-typography-tuned (full-width punctuation width, Chinese line breaking, a Chinese-first font stack, explicit east-asian font-slot declarations) from day one.
189
195
 
190
196
  ## License
191
197
 
package/README.zh-CN.md CHANGED
@@ -61,25 +61,28 @@ const bytes = await generatePptx(ir) // Uint8Array,可直接写成 .pptx 文
61
61
  | 命令 | 作用 |
62
62
  |---|---|
63
63
  | `render <target> -o <out.pptx> [--theme <id>] [--style <file>] [--draft]` | 校验并渲染成 `.pptx`——`target` 可以是 IR JSON 文件、deck 项目目录,或裸名(见「Deck 项目」) |
64
- | `validate <target>` | 校验 IR,输出带页码的错误信息——`target` 形式同 `render` |
65
- | `audit <target> [--json]` | 确定性几何审查(溢出/越界/低对比度/重叠)——`target` 形式同 `render`,一旦发现问题 exit 1(见「审查」) |
66
- | `plan validate <plan.json>` | 校验 deck plan 是否符合 schema 与随 mode 变化的硬门(见「Deck 项目」) |
64
+ | `validate <target>` | 校验 IR,输出带页码的错误信息与提示性警告——`target` 形式同 `render` |
65
+ | `audit <target> [--json] [--pixels]` | 确定性几何审查(溢出/越界/低对比度/重叠/内容截断/内容丢失)——`target` 形式同 `render`,一旦发现问题 exit 1(见「审查」) |
66
+ | `spec validate <spec.json>` | 校验 deck spec 是否符合 schema 与随 strategy 变化的硬门(见「Deck 项目」) |
67
67
  | `assemble <dir\|name> [-o <file>]` | 把 deck 项目目录合并成单个 IR JSON 文件 |
68
68
  | `disassemble <ir.json> -o <dir>` | 把 IR JSON 文件拆成 deck 项目目录 |
69
- | `schema [--style \| --plan]` | 输出 IR 的 JSON Schema(或 style 覆盖 schema,或 deck plan schema) |
69
+ | `schema [--style \| --spec]` | 输出 IR 的 JSON Schema(或 style 覆盖 schema,或 deck spec schema) |
70
70
  | `themes [--json]` | 列出 13 个内置主题 |
71
- | `scenarios [--json]` | 列出具名场景预设(mode/delivery/audience 轴 + theme 推荐) |
71
+ | `narratives [--json]` | 列出具名叙事预设(strategy/pacing/audience 轴 + theme 推荐) |
72
72
  | `preview <target> -o <dir> [--html]` | 逐页渲染为独立 SVG(`--html` 额外写出一个自包含的 `preview.html`)——`target` 形式同 `render`,永远不受占位页拦截 |
73
+ | `migrate <input> -o <output>` | 把 v3 IR 文件转成 v4,或把 `deck.plan.json` 项目目录转成 `deck.spec.json`——确定性转换,不调模型(见「IR」与「Deck 项目」) |
73
74
  | `init` | 生成 `pptfast.config.json` 模板 |
74
75
  | `check-update` / `self-update` | 检查 npm 上的新版本 / 更新全局安装 |
75
76
 
76
77
  ## IR
77
78
 
78
- 运行 `node dist/cli.js schema` 获取完整 JSON Schema——让模型写 IR 之前先读它。一份 deck(`PptxIR`)包含 `version`(现为 `"3"`)、`filename`、一个可选的 `scenario`(预设 id 字符串,或部分轴对象——详见下文「场景」一节)、`theme`(`id` 加可选的 `style`/`brand` 覆盖)、`meta`、`assets`——均可省略、有默认值——另外还有一个独立的可选 `brand`(logo 位置)字段,以及必填的有序 `slides` 列表。每张 slide 有一个 `type`(`cover`、`chapter`、`content`、`ending`)、一个可选的 `layout`(显式指定页面版式 id,一经设置恒生效、优先于自动选型——省略则由 pptfast 自动选型,详见下文「版式选型」)、一个可选的 `arrangement`(content slide 正文的排布方式,如 `two_column`、`kpi_focus`),以及一组带类型的 `components`(`bullets`、`kpi_cards`、`image`、`chart` 等)。`assets` 的形状是 `{ images: { [id]: { src, alt? } } }`,component 通过 `asset_id` 引用图片,同一张图可以在多页复用而不必重复内嵌。
79
+ 运行 `node dist/cli.js schema` 获取完整 JSON Schema——让模型写 IR 之前先读它。一份 deck(`PptxIR`)包含 `version`(现为 `"4"`,且省略时默认就是它)、`filename`、一个可选的 `narrative`(预设 id 字符串,或部分轴对象——详见下文「叙事」一节)、`theme`(`id` 加可选的 `style`/`brand` 覆盖)、`meta`、`assets`——均可省略、有默认值——另外还有一个独立的可选 `brand`(logo 位置)字段,以及必填的有序 `slides` 列表。每张 slide 有一个 `type`(`cover`、`chapter`、`content`、`ending`)、一个可选的 `layout`(显式指定页面版式 id,一经设置恒生效、优先于自动选型——省略则由 pptfast 自动选型,详见下文「版式选型」)、一个可选的 `arrangement`(content slide 正文的排布方式,如 `two_column`、`kpi_focus`),以及一组带类型的 `components`(`bullets`、`kpi_cards`、`image`、`chart` 等)。`assets` 的形状是 `{ images: { [id]: { src, alt? } } }`,component 通过 `asset_id` 引用图片,同一张图可以在多页复用而不必重复内嵌。
79
80
 
80
- 一份 deck 还可以携带一个可选的 `seed`(整数,让自动选型的版式在多次修订之间保持稳定——省略时如何生成,详见下文「版式选型」)。任意 slide 都可以设置一个稳定的 `id`(plan 的页面和校验报错都靠它引用)以及 `placeholder: true`(还没有内容的占位 slide——由 `assemble` 为 plan 里没人填写的页面注入,内容质量检查会跳过它,`render` 也会因它拒绝导出,除非加 `--draft`)。模型输出里容易和 schema 对不上的字段名(跨 component 类型共 25 组同义词,例如 kpi 的 `title`→`label`、quote 的 `content`→`text`)会在校验时静默改写成规范名——`validate`/`render`/`preview` 会打印一条改了什么的提示,从不因此报错。
81
+ 一份 deck 还可以携带一个可选的 `seed`(整数,让自动选型的版式在多次修订之间保持稳定——省略时如何生成,详见下文「版式选型」)。任意 slide 都可以设置一个稳定的 `id`(spec 的页面和校验报错都靠它引用)、`placeholder: true`(还没有内容的占位 slide——由 `assemble` 为 spec 里没人填写的页面注入,内容质量检查会跳过它,`render` 也会因它拒绝导出,除非加 `--draft`),以及一个可选的 `notes`(同义词 `note`/`speaker_notes`/`speakerNotes`),导出为原生 PowerPoint 演讲者备注——只是给主讲人自己看的内容,不会画到幻灯片画布上,也从不计入任何版式容量。模型输出里容易和 schema 对不上的字段名(跨 component 类型共 40 组同义词,例如 kpi 的 `title`→`label`、quote 的 `content`→`text`、swot 的 `strength`→`strengths`、bmc 的 `partners`→`key_partners`)会在校验时静默改写成规范名——`validate`/`render`/`preview` 会打印一条改了什么的提示,从不因此报错。这套救援机制只覆盖弱模型的同义词漂移,不覆盖 v4 之前的旧词汇。标着 v4 却仍写 `scenario`(而不是 `narrative`)、`mode`/`delivery`(而不是 `strategy`/`pacing`)、或轴值还停留在旧的 `narrative`/`text`/`presentation` 的文档,会像任何其他未识别字段或非法值一样直接硬报错,并列出当前正确的名称和取值。显式写 `version: "3"`(或 `"2"`)同样硬拒绝并给出迁移指引——见下文 `pptfast migrate`,这是旧词汇输入唯一支持的路径。
81
82
 
82
- v3 IR schema 0.3.0 起冻结——后续演进只走加法(新增可选字段、新增枚举值),任何破坏性变更都会启用新的顶层 `version` 值,并沿用 v2 那套硬拒绝 + 迁移提示的处理方式。
83
+ 四种 component 类型是「满幅」的:`swot`(strengths/weaknesses/opportunities/threats 四象限)、`bmc`(九宫格商业模式画布)、`waterfall`(运行合计瀑布图)、`gantt`(共享数轴上的甘特条形图)。各自独占整张 slide 的内容区域,必须是该 slide 唯一的 component——混入其他 component 会在校验时报错,而不是静默丢弃。
84
+
85
+ v4 IR schema 自 0.4.0 起冻结——后续演进只走加法(新增可选字段、新增枚举值),任何破坏性变更都会启用新的顶层 `version` 值,并沿用 v3 那套硬拒绝 + 迁移提示的处理方式。`pptfast migrate <v3-file.json> -o <out.json>` 能确定性地把一份 v3 文件转成 v4(只做字段改名——theme、版式选型、内容预算与视觉输出都不变)——`deck.plan.json` → `deck.spec.json` 的姊妹转换见下文「Deck 项目」。
83
86
 
84
87
  ## 主题
85
88
 
@@ -101,30 +104,30 @@ v3 IR schema 自 0.3.0 起冻结——后续演进只走加法(新增可选字
101
104
  | `luxe` | Luxe |
102
105
  | `heritage` | Heritage |
103
106
 
104
- ## 场景
107
+ ## 叙事
105
108
 
106
- 场景(scenario)是三条独立于主题(视觉风格)之外的叙事轴,用来定编辑纪律:`mode`(论证方式——`pyramid`、`narrative`、`instructional`、`showcase`、`briefing`)、`delivery`(内容密度——`text`、`balanced`、`presentation`)、`audience`(语气锚点——`executive`、`technical`、`customer`、`public`,目前无渲染效果)。把 IR 顶层的 `scenario` 设为具名预设字符串(如 `"boardroom-report"`),或部分轴对象(如 `{ "delivery": "presentation" }`)——省略任意一轴、或整个省略 `scenario` 字段,均回落到 `general` 预设(`briefing` × `balanced` × `public`)。未知的预设名或轴值会硬报错并列出可用项。
109
+ 叙事(narrative)是三条独立于主题(视觉风格)之外的轴,用来定编辑纪律:`strategy`(论证方式——`pyramid`、`storytelling`、`instructional`、`showcase`、`briefing`)、`pacing`(内容密度——`dense`、`balanced`、`spacious`)、`audience`(语气锚点——`executive`、`technical`、`customer`、`public`,目前无渲染效果)。把 IR 顶层的 `narrative` 设为具名预设字符串(如 `"boardroom-report"`),或部分轴对象(如 `{ "pacing": "spacious" }`)——省略任意一轴、或整个省略 `narrative` 字段,均回落到 `general` 预设(`briefing` × `balanced` × `public`)。未知的预设名或轴值会硬报错并列出可用项。
107
110
 
108
- `delivery` 驱动内容质量门,也驱动正文字号基线(仅 paragraph/bullets/callout 三件套——其余组件各自的字阶与标题体系不受影响):每页的 component 数预算与 bullets 预算(条目数与单条长度上限)都随 `delivery` 从 `text` 向 `presentation` 收紧,正文字号则反向增长——密度上限还会再叠加所选 layout 的容量,取两者中更紧的一个。
111
+ `pacing` 驱动内容质量门,也驱动正文字号基线(仅 paragraph/bullets/callout 三件套——其余组件各自的字阶与标题体系不受影响):每页的 component 数预算与 bullets 预算(条目数与单条长度上限)都随 `pacing` 从 `dense` 向 `spacious` 收紧,正文字号则反向增长——密度上限还会再叠加所选 layout 的容量,取两者中更紧的一个。这些是编辑性指导,不是硬限制——`validate` 只会把它们报成警告,仍然校验通过,真正能拦下生成的只有下面这条渲染安全上限。
109
112
 
110
- | delivery | 正文字号 | 每页 component 数 | bullets |
113
+ | pacing | 正文字号 | 每页 component 数 | bullets |
111
114
  |---|---|---|---|
112
- | `text` | 20px | 5 | 至多 6 条,每条约 48 字 |
115
+ | `dense` | 20px | 5 | 至多 6 条,每条约 48 字 |
113
116
  | `balanced`(默认) | 24px | 4 | 至多 5 条,每条约 40 字 |
114
- | `presentation` | 32px | 3 | 至多 4 条,每条约 30 字 |
117
+ | `spacious` | 32px | 3 | 至多 4 条,每条约 30 字 |
115
118
 
116
- bullets 需要时会在各自档位基线之下收缩以适配空间,最低到 14px 地板,再触发溢出处理。`pptfast validate` 会报出每页实际生效的具体数值。
119
+ bullets 需要时会在各自档位基线之下收缩以适配空间,最低到 14px 地板,再触发溢出处理。`default`、`plain`、`divided`、`numbered`、`checklist` 五种要点样式一视同仁:一条要点长到在这个地板字号下仍然放不下,会是一条硬校验错误——不同于(也宽于)上面按 pacing 分档的长度指导,因为它在渲染时会真的被省略号截掉文字,不只是显得啰嗦。`pptfast validate` 会报出每页实际生效的具体数值。
117
120
 
118
- 运行 `pptfast scenarios [--json]` 查看全部具名预设(各自带一份软性 theme 推荐表——仅供参考,从不构成约束)及三轴的原始数据表。
121
+ 运行 `pptfast narratives [--json]` 查看全部具名预设(各自带一份软性 theme 推荐表——仅供参考,从不构成约束)及三轴的原始数据表。
119
122
 
120
123
  ## 版式选型
121
124
 
122
- 当某页省略 `layout` 时,pptfast 按四个确定性步骤自动选型:该页型的全部注册版式 → 主题该页型的版式集合(默认全集,见上文「主题」)→ 场景的 `mode` 对一小撮适配该 mode 的 content 版式做 ×3 软加权,其余维持 ×1 底权(cover/chapter/ending 三个页型永不加权——它们的个性来自主题,不来自 mode)→ 按 seed 加权取样,若命中结果与紧邻的上一页版式相同则确定性地换成次优候选。显式 `layout` 恒优先,跳过以上全部步骤。内容装不装得下由 `validate` 的密度门单独把关,从不参与选型——因此改一页的内容不会悄悄翻转它的版式。
125
+ 当某页省略 `layout` 时,pptfast 按四个确定性步骤自动选型:该页型的全部注册版式 → 主题该页型的版式集合(默认全集,见上文「主题」)→ 叙事的 `strategy` 对一小撮适配该 strategy 的 content 版式做 ×3 软加权,其余维持 ×1 底权(cover/chapter/ending 三个页型永不加权——它们的个性来自主题,不来自 strategy)→ 按 seed 加权取样,若命中结果与紧邻的上一页版式相同则确定性地换成次优候选。显式 `layout` 恒优先,跳过以上全部步骤。内容装不装得下由 `validate` 的密度门单独标记(编辑性指导,不是硬拦截——见上文「叙事」),从不参与选型——因此改一页的内容不会悄悄翻转它的版式。
123
126
 
124
127
  选型本身完全确定——同一份 IR 永远选出同一个结果,预览与最终渲染绝不会不一致。但要在**多次修订之间**保持稳定(改一页不搅动其余页的自动选型),还需要一个持久化的 `seed`,按以下顺序解析:
125
128
 
126
129
  1. 显式 `ir.seed`——完全修订稳定,恒优先
127
- 2. deck 项目自己的 seed:plan 省略 `seed` 时,`pptfast assemble` 首次运行会用 plan 的 filename + 页面 id 列表派生一个,并打印提示——把这个值写进 `deck.plan.json` 的 `seed` 字段即可固化
130
+ 2. deck 项目自己的 seed:spec 省略 `seed` 时,`pptfast assemble` 首次运行会用 spec 的 filename + 页面 id 列表派生一个,并打印提示——把这个值写进 `deck.spec.json` 的 `seed` 字段即可固化
128
131
  3. 以上均未设置:回落到 `filename` + 每页 `heading` 的内容哈希(向后兼容旧行为)——改动任何一页标题都会重排全 deck 的自动选型
129
132
 
130
133
  `pptfast assemble` 还会把每一页的自动选型结果写回合并后的 `deck.json`(页面文件里已显式指定的 `layout` 不受影响)——CLI 会提示本次填写了多少页。
@@ -143,20 +146,22 @@ bullets 需要时会在各自档位基线之下收缩以适配空间,最低到
143
146
 
144
147
  ```
145
148
  my-deck/
146
- deck.plan.json 锁定的 plan:每一页的顺序、type、heading
147
- pages/<page-id>.json 每个已填页面一个文件(components/layout/arrangement/background/image_side/footnote)
149
+ deck.spec.json 锁定的 spec:每一页的顺序、type、heading
150
+ pages/<page-id>.json 每个已填页面一个文件(components/layout/arrangement/background/image_side/footnote)
148
151
  assets/ 本地图片,按文件名自动注册(图片 id = 去掉扩展名的文件名)
149
152
  ```
150
153
 
151
- `deck.plan.json` 可以在任何页面填写之前单独校验:`pptfast plan validate deck.plan.json` 会检查 schema,以及一组随 mode 变化的硬门(边界页类型、标题长度、节奏轮换、页数是否匹配 delivery)。plan 里某一页如果没有对应的 `pages/<id>.json`,会成为一个**占位页**——只有标题、不算缺失——所以写到一半的 deck 也能正常 assemble 和预览。`pptfast render` 遇到未填的占位页会拒绝导出,除非加 `--draft`。`pptfast preview` 则永远不会因占位页被拦。
154
+ `deck.spec.json` 可以在任何页面填写之前单独校验:`pptfast spec validate deck.spec.json` 会检查 schema,以及一组随 strategy 变化的硬门(边界页类型、标题长度、beat 轮换、页数是否匹配 pacing)。spec 里某一页如果没有对应的 `pages/<id>.json`,会成为一个**占位页**——只有标题、不算缺失——所以写到一半的 deck 也能正常 assemble 和预览。`pptfast render` 遇到未填的占位页会拒绝导出,除非加 `--draft`。`pptfast preview` 则永远不会因占位页被拦。目录里如果还留着改名前的 `deck.plan.json` 而不是 `deck.spec.json`,不会被直接读取——用 `pptfast migrate <dir> -o <dir>` 原地转换(会在旁边写出 `deck.spec.json`,不覆盖、也不删除原文件——确认新文件无误后自己删掉 `deck.plan.json`)。目录里两个文件同时存在会硬报错,绝不猜测优先级。
152
155
 
153
- `pptfast assemble <dir>` 把 plan + pages + assets 合并成一个 IR JSON 文件(默认写到 `deck.json`)。`pptfast disassemble <ir.json> -o <dir>` 做反向操作(有据可查的有损转换——`rhythm`/`focus` 这类只属于 plan 的字段在 IR 里没有对应位置,无法还原)。`render`/`validate`/`preview` 也都能直接接受一个目录,会先在内存里 assemble 一遍。
156
+ `pptfast assemble <dir>` 把 spec + pages + assets 合并成一个 IR JSON 文件(默认写到 `deck.json`)。`pptfast disassemble <ir.json> -o <dir>` 做反向操作(有据可查的有损转换——`beat`/`focus` 这类只属于 spec 的字段在 IR 里没有对应位置,无法还原)。`render`/`validate`/`preview` 也都能直接接受一个目录,会先在内存里 assemble 一遍。
154
157
 
155
158
  Deck 项目目录可以用裸名代替路径引用——`pptfast render my-deck -o out.pptx` 在本地找不到同名文件或目录时,会到 `$PPTFAST_HOME/decks` 下找 `my-deck`(`$PPTFAST_HOME` 缺省是 `~/.pptfast`)。所有 deck 默认值按四层优先级解析,从高到低:CLI flag > 项目级 `pptfast.config.json` > 用户级 `~/.pptfast/config.json` > deck 自身的值。两个配置层都可以设置 `decksDir` 来重定向裸名的解析位置——项目层的值相对该配置文件自身所在目录解析(给想把 deck 项目入库的团队用),用户层的值相对 `$PPTFAST_HOME` 解析,两者都设置时项目层优先。
156
159
 
157
160
  ## 审查
158
161
 
159
- `pptfast audit <target> [--json]` 会离屏渲染每一页,跑一遍确定性几何审查——不靠 LLM 截图目检,零方差。四类检查:**溢出**(文字超出自己的框或列)、**越界**(超出页面边缘)、**低对比度**(文字与其所在背景的 WCAG 相对亮度对比度不达标)、**重叠**(两个组件的区域大面积相交)。这是建议性工具,不是硬门——`validate` 已经拦住了结构非法或密度超标的 deck,audit 抓的是一份合法 deck 在渲染层仍可能出现的问题(作者选了一个贴近背景色的文字颜色、两个组件的内容恰好撞在一起)。
162
+ `pptfast audit <target> [--json]` 会离屏渲染每一页,跑一遍确定性几何审查——不靠 LLM 截图目检,零方差。六类检查:**溢出**(文字超出自己的框或列)、**越界**(超出页面边缘)、**低对比度**(文字与其所在背景的 WCAG 相对亮度对比度不达标)、**重叠**(两个组件的区域大面积相交)、**内容截断**(渲染器为适配版面用省略号截断了文字)、**内容丢失**(一个「+N 更多」标记——一张卡片列表或整个组件放不下被隐藏了)。这是建议性工具,不是硬门——`validate` 已经拦住了结构非法或密度超标的 deck,audit 抓的是一份合法 deck 在渲染层仍可能出现的问题(作者选了一个贴近背景色的文字颜色、两个组件的内容恰好撞在一起)。
163
+
164
+ 加上 `--pixels`(仅 Node,需要可选依赖 `sharp`)还能抓住 SVG 层检查天生看不到的一种情况——文字直接压在一张没有遮罩的照片背景上,做法是把该页光栅化成真实像素再采样。每次结果都会带一个 `checks` 字段(`{ svg: "completed", pixels: "not-requested" | "completed" }`),让调用方随时能分清「没查」和「查了没问题」,不会把未检查项误读成通过(像素层自身的跨平台一致性说明见 `docs/contrast-system.md`)。
160
165
 
161
166
  建议在所有页面填完之后跑一遍,`target` 形式同 `validate`/`render`(文件、deck 项目目录或裸名)。人读输出按页分组报错(`page 3 (p-kpi): [low-contrast] …`,每条消息都带修正建议),末尾附一行汇总。`--json` 输出完整的机器可读报告。exit code 本身即可供 agent 判断:干净是 `0`,发现问题是 `1`——按报错修那一页,再单独重跑一次 `audit` 即可,不必重新渲染。被跳过的占位页会在汇总里注明,和别处「不算缺失、只是还没写」的处理方式一致。
162
167
 
@@ -167,17 +172,18 @@ pptfast audit examples/basic.json
167
172
 
168
173
  ## 面向 AI agent
169
174
 
170
- 推荐给 agent 的生成回路:先读 `pptfast schema` 学词汇表,写出 IR JSON,跑 `pptfast validate` 并根据报错自纠(错误信息带页码和可直接照抄的修正方式,目的就是让这个回路不必依赖人工介入),再跑 `pptfast audit`——同样是可直接照抄的修正反馈,只是针对一份*合法* deck 在渲染层仍可能出现的问题(溢出、低对比度、重叠——exit code 本身就说明干不干净),最后执行 `pptfast render`。`pptfast preview` 能让 agent 在正式渲染前先看一遍 SVG,自查版式是否合理。加上 `--html` 还会额外写出一个自包含的 `preview.html`,供人工审查(键盘翻页、占位页角标——远程 URL 的图片资产仍是远程链接,这是自包含性上唯一的缺口)。上文的 Claude Code 插件已把这套回路封装成 skill([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md))。
175
+ 推荐给 agent 的生成回路:先读 `pptfast schema` 学词汇表,写出 IR JSON,跑 `pptfast validate` 并根据报错自纠(错误信息带页码和可直接照抄的修正方式,目的就是让这个回路不必依赖人工介入),再跑 `pptfast audit`——同样是可直接照抄的修正反馈,只是针对一份*合法* deck 在渲染层仍可能出现的问题(溢出、低对比度、重叠——exit code 本身就说明干不干净),最后执行 `pptfast render`。`pptfast preview` 能让 agent 在正式渲染前先看一遍 SVG,自查版式是否合理。加上 `--html` 还会额外写出一个自包含的 `preview.html`,供人工审查(键盘翻页、占位页角标——远程 URL 的图片资产仍是远程链接,这是自包含性上唯一的缺口)。当所有页面都已填写时,这份 `preview.html` 还会叠加同一份 `audit` 检查结果(每页一个数量角标,加一个可点击跳转的 findings 面板),让人工审查者不必打开终端就能看到问题——如果 deck 里还有占位页,则显示一行「audit 已跳过」的提示代替。审查者可以直接在 `preview.html` 里给每页写自由文本批注,并导出为 `revision-request.json`(浏览器 Blob 下载,不联网也不写文件——preview 始终只读),交给 agent 通过 `pages/*.json` 回填。上文的 Claude Code 插件已把这套回路封装成 skill([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md))。这套回路本身由一个模型无关的内部基准测试(`tests/bench/`,不发布到 npm)机械化验证——固定题库,评估模型跟随该 skill 的表现,细节见 `tests/bench/README.md`。
171
176
 
172
177
  ## 路线图
173
178
 
174
179
  - **v0.2**——封装该回路的 Claude Code plugin + skill(已落地)、design token 覆盖(`--style`)、`init`/自更新命令。
175
- - **v0.3**——场景化叙事轴(mode/delivery/audience)、显式 layout + component 注册表与加权 seed 选型、deck plan/assemble 工作流、确定性几何审查、自包含 HTML 预览、六阶段 skill(已落地,schema 已冻结——见上文「IR」一节)。
176
- - **v0.4**——主题生态(可分发主题注册表、主题定制 skill、自定义品牌插槽)、更丰富的动效(更多入场动画)、Office 真机实测、web playground、1.0 版本。
180
+ - **v0.3**——叙事驱动的轴(strategy/pacing/audience)、显式 layout + component 注册表与加权 seed 选型、deck spec/assemble 工作流、确定性几何审查、自包含 HTML 预览、六阶段 skill(已落地。这几条轴当时叫 `scenario`/`mode`/`delivery`,在下面 v0.4 的词汇重构里改名为 `narrative`/`strategy`/`pacing`,行为不变)。
181
+ - **v0.4**——vocabulary-v4 词汇重构:`scenario`→`narrative`、`mode`→`strategy`、`delivery`→`pacing`、`plan`→`spec`(`deck.plan.json`→`deck.spec.json`)、页面级 `rhythm`→`beat`,新增确定性的 `pptfast migrate` 命令,行为不变(已落地,schema 已冻结,自 0.4.0 起——见上文「IR」一节)。
182
+ - **v0.5+**——主题生态(可分发主题注册表、主题定制 skill、自定义品牌插槽)、更丰富的动效(更多入场动画)、Office 真机实测、web playground、1.0 版本。
177
183
 
178
184
  ## 致谢
179
185
 
180
- 图标原语抽取自 [lucide](https://lucide.dev)(ISC License)。pptfast 本身从一套生产环境的 AI 出 PPT 系统中抽取而来,从第一天起就针对 CJK 排版做了优化(全角标点宽度、中文换行、雅黑优先字体栈)。
186
+ 图标原语抽取自 [lucide](https://lucide.dev)(ISC License)。pptfast 本身从一套生产环境的 AI 出 PPT 系统中抽取而来,从第一天起就针对 CJK 排版做了优化(全角标点宽度、中文换行、雅黑优先字体栈、显式东亚字体槽声明)。
181
187
 
182
188
  ## License
183
189