@liustack/pptwise 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/README.zh-CN.md +136 -0
  4. package/cordis.patch.yml +5 -0
  5. package/dist/chunk-3ZUKISTY.js +114 -0
  6. package/dist/chunk-3ZUKISTY.js.map +1 -0
  7. package/dist/chunk-M35M4QUC.js +1167 -0
  8. package/dist/chunk-M35M4QUC.js.map +1 -0
  9. package/dist/chunk-VUOLBHD7.js +19 -0
  10. package/dist/chunk-VUOLBHD7.js.map +1 -0
  11. package/dist/chunk-WL5KWYKS.js +49762 -0
  12. package/dist/chunk-WL5KWYKS.js.map +1 -0
  13. package/dist/cli.js +4753 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/index.d.ts +4224 -0
  16. package/dist/index.js +99 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/node.d.ts +7 -0
  19. package/dist/node.js +11 -0
  20. package/dist/node.js.map +1 -0
  21. package/dist/pixel-audit-H5K6JK3X.js +218 -0
  22. package/dist/pixel-audit-H5K6JK3X.js.map +1 -0
  23. package/dist/registry-C0GJH7ZT.d.ts +46 -0
  24. package/dsh/client.js +1398 -0
  25. package/dsh/index.js +141 -0
  26. package/dsh/preview-tool.js +1931 -0
  27. package/dsh/spawnHidden.js +109 -0
  28. package/package.json +113 -0
  29. package/skills/pptwise/SKILL.md +100 -0
  30. package/skills/pptwise/SKILL.zh-CN.md +102 -0
  31. package/skills/pptwise/references/branding.md +18 -0
  32. package/skills/pptwise/references/branding.zh-CN.md +21 -0
  33. package/skills/pptwise/references/components.md +35 -0
  34. package/skills/pptwise/references/components.zh-CN.md +40 -0
  35. package/skills/pptwise/references/density.md +17 -0
  36. package/skills/pptwise/references/density.zh-CN.md +22 -0
  37. package/skills/pptwise/references/images.md +42 -0
  38. package/skills/pptwise/references/images.zh-CN.md +47 -0
  39. package/skills/pptwise/references/layouts.md +37 -0
  40. package/skills/pptwise/references/layouts.zh-CN.md +42 -0
  41. package/skills/pptwise/references/spec.md +107 -0
  42. package/skills/pptwise/references/spec.zh-CN.md +112 -0
  43. package/skills/pptwise/references/validate.md +82 -0
  44. package/skills/pptwise/references/validate.zh-CN.md +87 -0
  45. package/skills/pptwise/scripts/run.ps1 +192 -0
  46. package/skills/pptwise/scripts/run.sh +229 -0
@@ -0,0 +1,109 @@
1
+ // The only place this plugin starts a child process.
2
+ //
3
+ // The desktop app has no console of its own, so on Windows every child it
4
+ // starts would be given one and shown its window: a black box per preview
5
+ // (issue #60). `windowsHide` suppresses that, defaults to false in Node, and is
6
+ // ignored elsewhere.
7
+ //
8
+ // It lives in a file of its own, apart from its callers, so the rule can be
9
+ // checked by looking at which files reach `child_process` at all rather than at
10
+ // what each call passes. A call site cannot forget an option it never writes,
11
+ // and writing the option after the caller's leaves nothing to override it.
12
+ //
13
+ // The CLI has its own copy in src/cli/child.ts. The duplication is on
14
+ // purpose: this plugin ships as a unit and must not import from the CLI it
15
+ // drives.
16
+ import { spawn } from 'node:child_process'
17
+
18
+ const DRAIN_GRACE_MS = 500
19
+
20
+ export function spawnHidden(command, args, options) {
21
+ return spawn(command, args, { ...options, windowsHide: true })
22
+ }
23
+
24
+ /**
25
+ * Settle on `exit` plus a short drain, or on `close` if it arrives first.
26
+ * A grandchild that inherited stdout used to make `close` never fire (#1).
27
+ */
28
+ export function runChild(command, args, options = {}) {
29
+ const { timeoutMs, signal, ...spawnOptions } = options
30
+ return new Promise((resolve, reject) => {
31
+ const child = spawnHidden(command, args, {
32
+ ...spawnOptions,
33
+ stdio: ['ignore', 'pipe', 'pipe'],
34
+ })
35
+
36
+ let stdout = ''
37
+ let stderr = ''
38
+ let settled = false
39
+ let drainTimer
40
+ let timeoutTimer
41
+ let exitCode = null
42
+ let exited = false
43
+ let timedOut = false
44
+
45
+ const settle = (code) => {
46
+ if (settled) return
47
+ settled = true
48
+ if (timeoutTimer) clearTimeout(timeoutTimer)
49
+ if (drainTimer) clearTimeout(drainTimer)
50
+ child.stdout?.destroy()
51
+ child.stderr?.destroy()
52
+ child.unref()
53
+ signal?.removeEventListener('abort', onAbort)
54
+ if (timedOut) {
55
+ const error = new Error(`child process timed out after ${timeoutMs}ms`)
56
+ error.timedOut = true
57
+ reject(error)
58
+ return
59
+ }
60
+ resolve({ code: code ?? 0, stdout, stderr })
61
+ }
62
+
63
+ const restartDrain = () => {
64
+ if (!exited || settled) return
65
+ if (drainTimer) clearTimeout(drainTimer)
66
+ drainTimer = setTimeout(() => settle(exitCode), DRAIN_GRACE_MS)
67
+ }
68
+
69
+ child.stdout?.on('data', (chunk) => {
70
+ stdout += chunk
71
+ restartDrain()
72
+ })
73
+ child.stderr?.on('data', (chunk) => {
74
+ stderr += chunk
75
+ restartDrain()
76
+ })
77
+
78
+ child.on('error', (error) => {
79
+ if (settled) return
80
+ settled = true
81
+ if (timeoutTimer) clearTimeout(timeoutTimer)
82
+ if (drainTimer) clearTimeout(drainTimer)
83
+ signal?.removeEventListener('abort', onAbort)
84
+ reject(error)
85
+ })
86
+
87
+ child.on('exit', (code) => {
88
+ exitCode = code
89
+ exited = true
90
+ restartDrain()
91
+ })
92
+
93
+ child.on('close', (code) => settle(code))
94
+
95
+ if (timeoutMs !== undefined) {
96
+ timeoutTimer = setTimeout(() => {
97
+ timedOut = true
98
+ child.kill('SIGTERM')
99
+ settle(null)
100
+ }, timeoutMs)
101
+ }
102
+
103
+ const onAbort = () => child.kill()
104
+ if (signal) {
105
+ if (signal.aborted) onAbort()
106
+ else signal.addEventListener('abort', onAbort, { once: true })
107
+ }
108
+ })
109
+ }
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@liustack/pptwise",
3
+ "version": "0.22.0",
4
+ "description": "Stable, editable PPTX generation for AI agents — semantic IR in, native DrawingML out",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "license": "MIT",
8
+ "bin": {
9
+ "pptwise": "./dist/cli.js"
10
+ },
11
+ "main": "./dsh/index.js",
12
+ "exports": {
13
+ ".": "./dsh/index.js",
14
+ "./package.json": "./package.json",
15
+ "./client": "./dsh/client.js"
16
+ },
17
+ "dsh": {
18
+ "bundle": {
19
+ "patch": "./cordis.patch.yml"
20
+ },
21
+ "client": {
22
+ "inject": [
23
+ "@deepseek-ai/dsh-client-ui-tool"
24
+ ],
25
+ "platform": "web",
26
+ "immediately": true
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "dsh",
32
+ "cordis.patch.yml",
33
+ "skills/pptwise/SKILL.md",
34
+ "skills/pptwise/SKILL.zh-CN.md",
35
+ "skills/pptwise/scripts",
36
+ "skills/pptwise/references",
37
+ "README.zh-CN.md"
38
+ ],
39
+ "engines": {
40
+ "node": ">=22.19"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "rm -rf dist && tsup",
47
+ "typecheck": "tsc --noEmit",
48
+ "lint": "eslint src scripts tests dsh evals --no-error-on-unmatched-pattern",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "check": "pnpm typecheck && pnpm lint && pnpm test",
52
+ "e2e": "pnpm build && tsx scripts/e2e.mts",
53
+ "e2e:dsh": "tsx scripts/dsh-e2e.mts",
54
+ "bench:score": "tsx tests/bench/score.mts",
55
+ "docs:list": "node scripts/docs-list.js",
56
+ "gallery": "tsx scripts/gallery.mts",
57
+ "evals:gallery": "tsx evals/gallery/run.mts",
58
+ "fixtures:unassigned-bytes": "tsx scripts/write-unassigned-bytes.mts",
59
+ "prepublishOnly": "pnpm check && pnpm e2e",
60
+ "release:version": "changeset version && tsx scripts/sync-version.mts && tsx scripts/stamp.mts",
61
+ "bench:run": "tsx tests/bench/run.mts",
62
+ "bench:agentic": "tsx tests/bench/run-agentic.mts"
63
+ },
64
+ "keywords": [
65
+ "pptx",
66
+ "powerpoint",
67
+ "presentation",
68
+ "ai",
69
+ "agent",
70
+ "claude",
71
+ "svg",
72
+ "drawingml",
73
+ "cli",
74
+ "dsh",
75
+ "dsh-plugin"
76
+ ],
77
+ "author": "Leon Liu",
78
+ "repository": {
79
+ "type": "git",
80
+ "url": "git+https://github.com/liustack/pptwise.git"
81
+ },
82
+ "dependencies": {
83
+ "commander": "^13.1.0",
84
+ "jszip": "3.10.1",
85
+ "linkedom": "^0.18.12",
86
+ "pptxgenjs": "^4.0.1",
87
+ "react": "^19.2.4",
88
+ "react-dom": "^19.2.4",
89
+ "undici": "^8.10.0",
90
+ "zod": "^4.3.6"
91
+ },
92
+ "optionalDependencies": {
93
+ "sharp": "^0.35"
94
+ },
95
+ "devDependencies": {
96
+ "@changesets/cli": "^2.31.1",
97
+ "@eslint/js": "^9.39.0",
98
+ "@testing-library/jest-dom": "^6.9.1",
99
+ "@testing-library/react": "^16.3.2",
100
+ "@types/node": "^22.19.7",
101
+ "@types/react": "^19.2.14",
102
+ "@types/react-dom": "^19.2.3",
103
+ "eslint": "^9.39.0",
104
+ "eslint-plugin-react-refresh": "^0.5.3",
105
+ "jsdom": "^29.0.2",
106
+ "lucide": "^1.25.0",
107
+ "tsup": "^8.5.1",
108
+ "tsx": "^4.20.6",
109
+ "typescript": "~5.9.3",
110
+ "typescript-eslint": "^8.46.4",
111
+ "vitest": "^4.1.4"
112
+ }
113
+ }
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: pptwise
3
+ description: Generate a native, editable PPTX deck from an outline, notes, or a document using the pptwise CLI (semantic IR → validate → render). Use when the user asks to create a PPT, deck, presentation, or slides (做PPT/生成PPT/制作演示文稿/幻灯片) and wants a stable, editable, brand-consistent result rather than freeform drawn slides.
4
+ ---
5
+
6
+ # pptwise — deck generation playbook
7
+
8
+ pptwise turns a JSON IR (intermediate representation) into a native DrawingML `.pptx` — every shape stays editable in PowerPoint. You own the content model. The tool owns layout, style, and motion. You never draw SVG or position anything: pick from a controlled vocabulary and let the validate gate catch what will not fit.
9
+
10
+ ## Run it
11
+
12
+ Everything in this playbook runs through the CLI: schema, spec/assemble, validate, render, audit, preview, serve, brand extract. Every one of those commands goes through the launcher bundled with this skill, which resolves a working runtime for you. Replace `<skill-dir>` with the directory this SKILL.md lives in:
13
+
14
+ ```bash
15
+ bash <skill-dir>/scripts/run.sh <args> # macOS / Linux
16
+ powershell -ExecutionPolicy Bypass -File <skill-dir>\scripts\run.ps1 <args> # Windows
17
+ ```
18
+
19
+ It tries a compatible `pptwise` on `PATH` first, then `npx`, then `bunx`, forwarding your arguments and its exit code unchanged. Nothing to install first, and the version it runs is pinned to this skill. Exit 78 means no runtime at all: relay the `nextSteps` from its stderr JSON instead of retrying.
20
+
21
+ Wherever this playbook writes `pptwise <args>`, run it through that launcher.
22
+
23
+ Right after an install, and any time a command misbehaves in a way the error message does not explain, run `pptwise doctor` before anything else. It reports the runtime, every installed skill copy and whether one is stale, the dsh plugin's version, which optional capabilities are present, and a self-test render. Relay what it says instead of guessing.
24
+
25
+ If your harness forbids running scripts, work down the same order by hand and use the first line that applies:
26
+
27
+ 1. A `pptwise` on `PATH` at the same major version as the pin below and no older: `pptwise <args>`.
28
+ 2. Otherwise, if `npx` exists: `npx --yes --package @liustack/pptwise@0.22.0 pptwise <args>`.
29
+ 3. Otherwise, if `bunx` exists: `bunx --bun @liustack/pptwise@0.22.0 <args>`.
30
+ 4. Otherwise tell the user no JavaScript runtime was found, and that installing Node 22.19+ (https://nodejs.org) or Bun (https://bun.sh) is the next step. Do not report pptwise itself as broken.
31
+
32
+ ## Workflow
33
+
34
+ Interview → spec → pages → validate → audit → render. Re-enter at the smallest step that captures a change. A very small deck (a handful of slides) may skip the spec file and write a single IR, still validating with `pptwise validate`. Never write IR or a spec from memory of a previous session or from this file. Run these fresh every session:
35
+
36
+ ```bash
37
+ pptwise schema # IR JSON Schema: the single source of truth
38
+ pptwise schema --spec # deck spec schema
39
+ pptwise narratives --json # named narrative presets (strategy/pacing/audience axes + theme recommendations)
40
+ pptwise themes --json # built-in themes (id + label)
41
+ ```
42
+
43
+ Also scan the workspace before asking anyone anything. A confirmed `deck.spec.json` already locks narrative, theme, and branding: do not re-interview, revise that deck instead. A `theme.json`, pinned `pptwise.config.json` theme, user-named theme id, or supplied `.thmx` / `.potx` / branded `.pptx` is a brand signal: extract or honor it. Do not ask whether a template exists.
44
+
45
+ **Boundary-page rule:** `chapter` and `ending` pages never render `components` or `footnote`. `cover` pages never render `footnote`. A `cover` may carry `components` only when its locked layout declares a slot for them. Today that is `verdict-index` (consulting), which reads the first `bullets` block as up to three numbered arguments. Every other cover layout still drops components. Put body content on a `content` page unless you are filling that consulting argument row. Wrong/right JSON and spec writing: `references/spec.md`.
46
+
47
+ 1. **Interview** (at most one round) when a user is present and any of audience, how it is told, or pacing is still unknown. Relay unresolved questions in **one** message, then stop. Do not fill them in. Q1–Q4, ★ defaults, lookup, `NARRATIVE_INTERVIEW` gate: `references/spec.md`.
48
+ 2. **Spec and confirm** before any page content. Write `deck.spec.json` (opens on `cover`, closes on `ending`, everything in between is `content` or `chapter`). Run `pptwise spec validate` until `OK`, then persist a `seed`. Do not re-spec a confirmed spec. How to write it: `references/spec.md`. Branding posture: `references/branding.md`.
49
+ 3. **Pages** in batches of at most 4. Write `pages/<id>.json` (`components`, optional `layout`/`notes`). Never write `type`/`heading`. Pin-only and sparse climax layouts: `references/layouts.md`. Component forms: `references/components.md`. Density, beat, capacity: `references/density.md`. Images: `references/images.md`.
50
+ 4. **Validate** after every batch: `pptwise assemble deck-dir/` then `pptwise validate deck-dir/` until both print `OK`. Restructure flagged content, never delete it. The assemble/validate/audit/preview/serve loop: `references/validate.md`.
51
+ 5. **Audit** once every page is filled: `pptwise audit deck-dir/` until exit 0. Do not substitute a screenshot. Then hand the deck over (`pptwise_preview`, else `preview --html`, else `serve --no-open`): `references/validate.md`.
52
+ 6. **Render:** `pptwise render deck-dir/`. Report the absolute path it prints. `--draft` and `--allow-dropped-content` only when the user says so.
53
+
54
+ Follow-up: edit a page → steps 3–6 on that file only. A new deck → step 1. Unrelated → do not invoke pptwise.
55
+
56
+ ## Component selection
57
+
58
+ | Content shape | Use | Not |
59
+ |---|---|---|
60
+ | 2–5 headline metrics | `kpi_cards` | `chart` |
61
+ | Series data (trend, comparison, share) | `chart` (`bar`/`line`/`pie`/`funnel`/`dumbbell`/`scatter`/`area`/`donut`/`gauge`) | numbers buried in `bullets` |
62
+ | Exact figures the audience reads row-by-row (price list, spec sheet, metrics-by-period grid) | `data_table` | `chart` |
63
+ | Linear process, no branches | `steps` | `flowchart` |
64
+ | Branching process that reaches an endpoint | `flowchart` | `steps` |
65
+ | Cyclical process with no endpoint — loops back to its own start (PDCA, a product lifecycle, a flywheel, a seasonal cycle) | `cycle` | `flowchart` |
66
+ | Two-sided contrast | `comparison` | two bullet lists |
67
+ | System/organizational layering (a stack of bands, e.g. tech-stack layers or a maturity ladder) | `architecture` | `bullets` |
68
+ | Dated milestones | `timeline` | `bullets` with dates |
69
+ | Phased plan with workstreams | `roadmap` | `timeline` |
70
+ | Phased plan with dated bars on a shared axis | `gantt` | `roadmap` |
71
+ | One verdict or takeaway sentence | `verdict_banner` or `callout` | `paragraph` |
72
+ | 2×2 strategic assessment (strengths/weaknesses/opportunities/threats) | `swot` | `matrix` |
73
+ | 9-block business model canvas | `bmc` | separate `bullets`/`row_cards` |
74
+ | Cumulative bridge/variance breakdown | `waterfall` | `chart` |
75
+ | 2×2 macro-environment scan (political/economic/social/technological) | `pest` | `swot` |
76
+ | Competitive-structure analysis (rivalry + 4 surrounding forces) | `five_forces` | `matrix` |
77
+ | Two-axis value grid with color-coded cells (e.g. region × quarter) | `heatmap` | `matrix` |
78
+ | Proportional flow/quantity distribution across stages (e.g. budget allocation, energy mix) | `sankey` | `chart` (funnel) or `flowchart` |
79
+ | A product/software screenshot that the slide needs to read as "this is real, running software" (an app dashboard, a live product UI) | `device_mockup` | `image` |
80
+ | A roster of people (team, speaker lineup, judging panel, author list) needing an identity anchor with no photo available | `people_cards` | `row_cards`/`icon_cards` |
81
+ | A set of short parallel labels (a tech stack, capabilities, keywords, certifications) — labels, not described items | `tag_row` | `bullets`/`row_cards` |
82
+
83
+ Lookalike pairs, field notes, and full-body types: `references/components.md`.
84
+
85
+ ## Rules
86
+
87
+ - Never edit or post-process the generated `.pptx`
88
+ - Never bypass a `validate` error by deleting the content it flagged — restructure it (split the slide, tighten the heading, pick a denser component type)
89
+ - Public deck text follows the user's language, IR structural fields are always the English enum values from the schema
90
+ - Never tell a user that a `chart`'s or `data_table`'s numbers are editable inside PowerPoint: those components render as grouped shapes and text, fully restylable and retypable, but with no native chart part or `<a:tbl>` behind them. To change the numbers, edit the IR and re-render.
91
+
92
+ ## Read when
93
+
94
+ - `references/spec.md` — writing `deck.spec.json`, choosing page types, or running the narrative interview
95
+ - `references/layouts.md` — pinning a layout, including climax, quote, and evidence sparse pages
96
+ - `references/components.md` — a lookalike pair or a component's fields and limits
97
+ - `references/density.md` — pacing budgets, `beat`, capacity warnings, or slide `decor`
98
+ - `references/branding.md` — extracting a company template, or whether to write `branding: "full"`
99
+ - `references/images.md` — declaring assets, searching stock, or generating art
100
+ - `references/validate.md` — assemble / validate / audit / preview / serve, or revising a page
@@ -0,0 +1,102 @@
1
+ ---
2
+ summary: 'skills/pptwise/SKILL.md 的中文阅读镜像,仅供人工审阅该 skill 会指示 agent 做什么'
3
+ mirror_of: skills/pptwise/SKILL.md
4
+ ---
5
+
6
+ # pptwise — deck 生成操作手册
7
+
8
+ > 本文件是 [`skills/pptwise/SKILL.md`](./SKILL.md) 的中文阅读镜像,供中文使用者审阅这个 skill 会指示 agent 执行的内容。agent 始终加载并执行英文版 `SKILL.md`——本文件不含 `name` 字段,从不注册为一个独立的 skill,也从不被 agent 读取。两个文件如有出入,以英文版 `SKILL.md` 为准。修改任一文件时,必须把改动同步镜像到另一文件。
9
+
10
+ pptwise 把一份 JSON IR(intermediate representation,中间表示)转换成原生 DrawingML 格式的 `.pptx`——每个图形在 PowerPoint 里都保持可编辑。内容模型由你掌控,layout、style 与动效由工具掌控。你从不绘制 SVG,也从不给任何东西定位:从受控词汇表里挑选,装不下的内容交给 validate 关卡去拦。
11
+
12
+ ## 怎么跑
13
+
14
+ 这份操作手册里的每一步都走 CLI:schema、spec/assemble、validate、render、audit、preview、serve、品牌提取。这些命令一律通过本 skill 自带的启动器执行,由它替你解析出一个可用的运行时。把 `<skill-dir>` 换成这份 SKILL.md 所在的目录:
15
+
16
+ ```bash
17
+ bash <skill-dir>/scripts/run.sh <args> # macOS / Linux
18
+ powershell -ExecutionPolicy Bypass -File <skill-dir>\scripts\run.ps1 <args> # Windows
19
+ ```
20
+
21
+ 它按顺序尝试:PATH 上版本兼容的 `pptwise`、`npx`、`bunx`,参数与退出码原样透传。不需要预先安装任何东西,跑到的版本被钉死在这份 skill 上。退出码 78 表示没有任何可用运行时:把它 stderr 里 JSON 的 `nextSteps` 转告用户,不要重试。
22
+
23
+ 下文凡是写 `pptwise <args>` 的地方,都通过这个启动器执行。
24
+
25
+ 刚装完,以及任何时候某条命令的表现不对、错误信息又解释不清时,先跑 `pptwise doctor`。它会报告运行时、机器上每一份已安装的 skill 副本及其是否过期、dsh 插件版本、可选能力是否具备,以及一次自检渲染。把它说的原样转达,不要靠猜。
26
+
27
+ 如果你的 harness 不允许执行脚本,就按同样的顺序自己判断,用第一条成立的:
28
+
29
+ 1. PATH 上有 `pptwise`,且主版本号与下面的钉版本相同、版本不低于它:`pptwise <args>`。
30
+ 2. 否则,有 `npx` 就用:`npx --yes --package @liustack/pptwise@0.22.0 pptwise <args>`。
31
+ 3. 否则,有 `bunx` 就用:`bunx --bun @liustack/pptwise@0.22.0 <args>`。
32
+ 4. 都没有就告诉用户机器上找不到 JavaScript 运行时,下一步是装 Node 22.19+(https://nodejs.org)或 Bun(https://bun.sh)。不要说成是 pptwise 本身坏了。
33
+
34
+ ## 工作流程
35
+
36
+ 访谈 → spec → pages → validate → audit → render。改动从能承载它的最小一步重新进入。很小的 deck(页数屈指可数)可以跳过 spec 文件,直接写一份 IR,仍用 `pptwise validate` 校验。永远不要凭上一个 session 的记忆、或凭这份文件本身的记忆去写 IR 或 spec。每个 session 都重新跑:
37
+
38
+ ```bash
39
+ pptwise schema # IR JSON Schema: the single source of truth
40
+ pptwise schema --spec # deck spec schema
41
+ pptwise narratives --json # named narrative presets (strategy/pacing/audience axes + theme recommendations)
42
+ pptwise themes --json # built-in themes (id + label)
43
+ ```
44
+
45
+ 动手问人之前,先扫工作区。已有确认过的 `deck.spec.json` 已经锁死 narrative、theme、品牌框:不要重做访谈,改那份 deck。已有 `theme.json`、项目 `pptwise.config.json` 钉死的 theme、用户点名的 theme id、或用户递来的 `.thmx` / `.potx` / 带品牌 `.pptx`,都是品牌信号:抽取或沿用。不要再问有没有模板。
46
+
47
+ **边界页规则:** `chapter` 和 `ending` 永远不渲染 `components` 或 `footnote`。`cover` 永远不渲染 `footnote`。封面只有在锁定版式声明了对应槽位时才能带 `components`。今天这只发生在 `verdict-index`(consulting):它读第一个 `bullets` 块,画成最多三条编号论据。其余封面版式仍会丢掉 components。正文放到 `content` 页,除非你在填 consulting 封面那三列论据。对错 JSON 和 spec 写法:`references/spec.md`。
48
+
49
+ 1. **访谈**(最多一轮):用户在场,且受众、怎么讲、pacing 任一轴仍未知时,把未决的问放进**一条**消息,然后停。不要自己填。Q1–Q4、★ 默认、查表、`NARRATIVE_INTERVIEW` 闸:`references/spec.md`。
50
+ 2. **定 spec 并确认**,再写任何页面。写 `deck.spec.json`(以 `cover` 开篇,以 `ending` 收尾,中间是 `content` 或 `chapter`)。跑 `pptwise spec validate` 直到 `OK`,然后固化 `seed`。已确认的 spec 不要重定。写法:`references/spec.md`。品牌框姿态:`references/branding.md`。
51
+ 3. **填页面**,每批至多 4 页。写 `pages/<id>.json`(`components`,可选 `layout`/`notes`)。绝不写 `type`/`heading`。Pin-only 与稀排高潮页:`references/layouts.md`。组件形态:`references/components.md`。密度、beat、容量:`references/density.md`。配图:`references/images.md`。
52
+ 4. **Validate** 每批之后:`pptwise assemble deck-dir/`,再 `pptwise validate deck-dir/`,直到两者都打印 `OK`。重组被标出的内容,不要删。assemble / validate / audit / preview / serve 回路:`references/validate.md`。
53
+ 5. **Audit** 所有页面填完后:`pptwise audit deck-dir/` 直到 exit 0。不要用截图代替。然后把 deck 交给用户(有 `pptwise_preview` 就调它,否则 `preview --html`,再否则 `serve --no-open`):`references/validate.md`。
54
+ 6. **渲染:** `pptwise render deck-dir/`。把打印的绝对路径报给用户。`--draft` 和 `--allow-dropped-content` 只有用户明确要求时才用。
55
+
56
+ 后续请求:改一页 → 只对那一页走步骤 3–6。一份新 deck → 步骤 1。和 deck 生成无关 → 不要调用 pptwise。
57
+
58
+ ## 组件选型
59
+
60
+ | 内容形态 | 用 | 不用 |
61
+ |---|---|---|
62
+ | 2–5 项头条指标 | `kpi_cards` | `chart` |
63
+ | 系列数据(趋势、对比、占比) | `chart`(`bar`/`line`/`pie`/`funnel`/`dumbbell`/`scatter`/`area`/`donut`/`gauge`) | 埋在 `bullets` 里的数字 |
64
+ | 受众要逐行读的精确数字(价目表、规格表、按周期分列的指标网格) | `data_table` | `chart` |
65
+ | 线性流程,无分支 | `steps` | `flowchart` |
66
+ | 有分支、且最终走到终点的流程 | `flowchart` | `steps` |
67
+ | 循环往复、没有终点的流程(首尾相连回到起点,如 PDCA、产品生命周期、飞轮、季节性循环) | `cycle` | `flowchart` |
68
+ | 双方对比 | `comparison` | 两份 bullet 列表 |
69
+ | 系统/组织分层(一叠层带,例如技术栈分层或成熟度阶梯) | `architecture` | `bullets` |
70
+ | 有日期的里程碑 | `timeline` | 带日期的 `bullets` |
71
+ | 分阶段计划,带多条工作线 | `roadmap` | `timeline` |
72
+ | 分阶段计划,在共享坐标轴上画出带日期的条形 | `gantt` | `roadmap` |
73
+ | 一句结论或要点 | `verdict_banner` 或 `callout` | `paragraph` |
74
+ | 2×2 战略评估(优势/劣势/机会/威胁) | `swot` | `matrix` |
75
+ | 9 宫格商业模式画布 | `bmc` | 拆开的 `bullets`/`row_cards` |
76
+ | 累计合计的桥接/差异拆解 | `waterfall` | `chart` |
77
+ | 2×2 宏观环境扫描(政治/经济/社会/技术) | `pest` | `swot` |
78
+ | 竞争结构分析(竞争强度 + 周边 4 种力量) | `five_forces` | `matrix` |
79
+ | 双轴数值网格,按颜色编码单元格(例如地区 × 季度) | `heatmap` | `matrix` |
80
+ | 跨阶段的比例流量/数量分布(例如预算分配、能源结构) | `sankey` | `chart`(funnel)或 `flowchart` |
81
+ | 产品/软件截图,这张 slide 要让人一眼认出「这是真实、正在运行的软件」(App 仪表盘、真实产品界面) | `device_mockup` | `image` |
82
+ | 一份人员名单(团队、讲者阵容、评委阵容、作者名单),需要一个无照片可用的身份锚点 | `people_cards` | `row_cards`/`icon_cards` |
83
+ | 一组短平行标签(技术栈、能力清单、关键词、资质认证)——是标签,不是带描述的条目 | `tag_row` | `bullets`/`row_cards` |
84
+
85
+ 形态对照、字段说明、满幅组件:`references/components.md`。
86
+
87
+ ## 规则
88
+
89
+ - 从不编辑或后处理生成出来的 `.pptx`
90
+ - 从不通过删除 `validate` 报错所指的内容来绕过它——去重组它(拆分 slide、收紧标题、换一个更紧凑的 component 类型)
91
+ - 面向用户的 deck 文本跟随用户使用的语言,IR 的结构性字段永远用 schema 里的英文枚举值
92
+ - 从不告诉用户 `chart`、`data_table` 里的数字可以在 PowerPoint 里直接编辑。这两类组件渲染出来是成组的图形加文字,样式和文字都能自由改,但背后没有原生的图表部件,也没有 `<a:tbl>`。要改数字,去改 IR 再重新渲染
93
+
94
+ ## 何时去读
95
+
96
+ - `references/spec.md` — 写 `deck.spec.json`、选页型、或做叙事访谈时
97
+ - `references/layouts.md` — 钉 layout,包括高潮页、金句页、证据页稀排版式时
98
+ - `references/components.md` — 碰到形态相近的组件,或要看字段与上下限时
99
+ - `references/density.md` — 处理 pacing 预算、`beat`、容量警告、或 slide `decor` 时
100
+ - `references/branding.md` — 抽取公司模板,或决定要不要写 `branding: "full"` 时
101
+ - `references/images.md` — 声明资产、搜图库、或生图时
102
+ - `references/validate.md` — 跑 assemble / validate / audit / preview / serve,或修订某一页时
@@ -0,0 +1,18 @@
1
+ # Branding posture
2
+
3
+ Read this when extracting a company template, or deciding whether to write `branding: "full"`.
4
+
5
+ A brand signal answers what the deck should look like, never how it should argue. Turning "this company's palette looks like a consulting firm" into a narrative is a guess wearing a fact's clothes, and it is how a deck ends up arguing in a shape nobody chose.
6
+
7
+ ## Brand themes — the user's own company template
8
+
9
+ When the user hands over (or mentions having) a company template — a `.thmx` theme, `.potx` template, or any branded `.pptx` — extract its colors and fonts into a custom theme **before** picking a built-in theme in phase 2. Extraction runs entirely locally; the file never leaves the machine.
10
+
11
+ ```bash
12
+ pptwise brand extract corp-template.pptx -o deck-dir/theme.json --id acme
13
+ pptwise render deck-dir/ # theme.json auto-loads; set "theme": "acme" in deck.spec.json
14
+ ```
15
+
16
+ A `theme.json` sitting in the deck project directory auto-loads on every command (validate/render/audit/preview/serve) — reference its id from `deck.spec.json` and no flag is needed. For a single IR file, pass `--theme-file deck-dir/theme.json` instead (works on the same five commands). Loading enforces a contrast floor: a template whose text/background tones are too close is refused with the failing token and ratio named — relay that message and ask the user whether to adjust the extracted file's colors or fall back to a built-in theme.
17
+
18
+ Leave `branding` off the spec and the IR unless every content page needs the brand footer. Write `branding: "full"` whenever `meta.confidentiality` is `confidential` or `restricted`, or the file needs an organization colophon. Confidentiality and date then appear on the cover. They stay off every other posture.
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: 'skills/pptwise/references/branding.md 的中文阅读镜像'
3
+ mirror_of: skills/pptwise/references/branding.md
4
+ ---
5
+
6
+ # Branding 姿态
7
+
8
+ 何时读:抽取公司模板,或决定要不要写 `branding: "full"` 时。
9
+
10
+ 品牌信号回答的是这份 deck 长什么样,从来不回答它该怎么论证。把「这家公司的配色像咨询公司」读成一种叙事,是把推断当事实抬上来,一份没人选过的论证形状就是这样上台的。
11
+
12
+ ## 品牌主题——用户自己的公司模板
13
+
14
+ 当用户递来(或提到手头有)公司模板——`.thmx` 主题、`.potx` 模板,或任何带品牌的 `.pptx`——先把它的配色和字体抽成自定义 theme,**再**进入阶段二的 theme 决策。抽取完全在本地进行,文件从不离开这台机器。
15
+
16
+ ```bash
17
+ pptwise brand extract corp-template.pptx -o deck-dir/theme.json --id acme
18
+ pptwise render deck-dir/ # theme.json 自动装载。在 deck.spec.json 里写 "theme": "acme"
19
+ ```
20
+
21
+ spec 和 IR 不要写 `branding`,除非每一页内容页都需要品牌页脚。`meta.confidentiality` 为 `confidential` 或 `restricted`,或文件需要机构落款时,写 `branding: "full"`。密级和日期随后出现在封面。其余姿态不出现。
@@ -0,0 +1,35 @@
1
+ # Component guide
2
+
3
+ Read this when choosing among lookalike components, or when a component's fields and floor/ceiling matter.
4
+
5
+ `steps` vs `flowchart` is the most common miss: if the edges never branch, it is `steps`. `flowchart` vs `cycle` is the next: does the process reach an endpoint, or does it loop back to its own start? Forcing a closed loop into `flowchart` makes the closing edge draw as a stray line/arc crossing the whole diagram — it isn't a diagram bug, it's the wrong component; reach for `cycle` the moment the last stage's arrow points back at the first. `roadmap` vs `gantt` is the next: `roadmap` groups workstreams into swimlanes with no shared numeric axis, `gantt` plots dated bars against one shared axis all items compare against. `pest` vs `swot` is the next: `pest` is external macro-environment factors only (no internal strengths/weaknesses axis), always the same four named categories — an internal-vs-external strategic assessment is still `swot`. `sankey` vs `flowchart`/funnel `chart` is the next: `sankey` conserves and splits a quantity across branching/merging paths (the band width itself carries meaning), `flowchart` is decision/process branching with no quantity, and a funnel `chart` only ever narrows in one line, never branches or merges. `data_table` vs `chart` vs `comparison` is the last: exact figures the audience reads row-by-row is `data_table`, a trend/comparison shape meant to be read at a glance is `chart`, qualitative side-by-side attributes with no exact figures is `comparison`.
6
+
7
+ Inside `chart`, the subtype is the shape of the data. `scatter` when both axes are quantities (give each point an optional `size` to make it a bubble chart). `area` when a line's filled region should read as accumulation or volume. `donut` for a part-to-whole share, with an optional total printed big in its center (`center_total: true`). `gauge` for one value's progress toward a target. `gauge` vs `kpi_cards` is the one to get right: a `gauge` is a single completion metric drawn as a filled half-ring (62% of goal), while `kpi_cards` is several independent headline numbers set side by side, so never build a row of gauges where `kpi_cards` belongs. `scatter` vs `line`: `scatter` needs a numeric x (a real coordinate on both axes), a category-labelled x-axis is still `line`.
8
+
9
+ `architecture`'s `layers` array paints top-to-bottom by default (`layers[0]` is the topmost band) — the natural order for a system stack authored top-down (presentation layer first, infrastructure last). Author a bottom-up narrative (a maturity ladder, a foundation-first capability model) in its own natural low-to-high order and set `direction: "bottom_up"` on the component to paint `layers[0]` at the bottom instead — do not hand-reverse the array to fake it, the field exists precisely so the array stays in narrative order.
10
+
11
+ `swot`/`bmc`/`waterfall`/`gantt`/`pest`/`five_forces`/`heatmap`/`sankey` are *full-body*: each fills the entire slide and must be the slide's only component — see `references/density.md`.
12
+
13
+ ### Cycles vs. flowcharts
14
+
15
+ Both draw a sequence of stages connected by arrows — the split is whether the process has an endpoint. `flowchart` is for a process that starts somewhere and finishes somewhere, even if it branches on the way; forcing a closed loop through it means adding an edge from the last node back to the first, and `flowchart`'s layout engine has no notion that this edge is special — it draws as a long stray line or arc crossing the whole diagram, reading like a mistake, not "this repeats". `cycle` is for a process that has no endpoint: it always returns to its own start (PDCA, a product lifecycle, a flywheel, a seasonal cycle, "design → build → review → design"). The test: does the last stage's arrow point at something new, or at the first stage again? Pointing at the first stage again is `cycle`, full stop.
16
+
17
+ Fields: `items` (3-8 entries, each a required `label` and an optional `description`), an optional overall `title`. `cycle` accepts no `direction` field (stages always run clockwise — write `items` in that reading order) and no center-text slot; keep the diagram to the stages themselves and put anything else in the surrounding page text. 3 is a hard floor (2 stages can't visually close into a ring — use `flowchart` or `steps` instead) and 8 is a hard ceiling (a 9th node crowds the ring past legible size on a 1280x720 slide — split into multiple `cycle` slides instead of cramming more stages onto one ring).
18
+
19
+ ### Device mockups vs. plain images
20
+
21
+ `device_mockup` frames an asset inside a themed browser-window or device frame instead of a bare bordered rect — it exists for exactly one job: a screenshot that has to be read as "a real product, actually running", not "a picture on a slide". Reach for it when the content is a screenshot of software/an app/a dashboard and the page's own point is that this product is real and working today. Keep plain `image` for everything else — ordinary photos, diagrams, illustrations, or a screenshot used only to illustrate a point in passing, not to assert "this is live". Overusing `device_mockup` on content that isn't actually a product screenshot reads as a strange decorative border, not evidence.
22
+
23
+ Fields: `device` (`"browser"` or `"phone"`, required — pptwise doesn't guess), `asset_id` (same semantics as `image`), an optional `caption`, and — `browser` only — an optional `url` that renders as the address-bar text (the single strongest "this is really running in a browser" signal available; a `phone` mockup has no address bar, so `validate` hard-rejects `url` set on one). The screen always crops to fill the frame (cover) — there's no `fit` choice, unlike `image`: a real device's screen fills edge to edge. No other decoration options exist on purpose — no tilt/perspective, no dark-frame toggle, no side-by-side multi-device layout; the theme's own tokens pick the frame colors.
24
+
25
+ ### People rosters vs. row/icon cards
26
+
27
+ The test is simple: is every item a *person*? A team roster, a speaker lineup, a judging panel, an author list — `people_cards` lays 2-12 people out on an equal-weight card grid, each card a deterministic initials badge (derived from the person's `name`, no photo asset needed) plus name and optional `role`/`org`. Keep `row_cards`/`icon_cards` for non-person enumerations — features, milestones, product topics — even when they happen to carry the same name/description-shaped fields; those two cap out at 6 items each, `people_cards` at 12, so a list of people that would blow through that cap (a 9-speaker conference lineup, say) is the clearest sign it belongs on `people_cards` instead of forced into two unlabeled row_cards pages.
28
+
29
+ Fields: `people` (2-12 entries, each a required `name` and optional `role`/`org`), an optional overall `title`. The initials badge is a pure function of `name`: a Latin name takes the first letter of its first two words ("Sarah Chen" → "SC"), a single Latin word takes its own first two letters, and a CJK name takes only its first character — the surname — never two ("王小明" → "王"). There is no photo field on purpose: a slide with real headshots already has `image_grid`, and `people_cards`'s entire reason to exist is the zero-asset initials badge. 2 is a hard floor (a single person's bio doesn't need a grid — use `callout` or plain text) and 12 is a hard ceiling (a larger roster splits across multiple `people_cards` slides instead of cramming a 13th+ card onto one grid).
30
+
31
+ ### Tag rows vs. bullets and cards
32
+
33
+ A row of short parallel labels — a technology stack, a capability or skill set, a keyword set, the certifications a vendor holds — is `tag_row`, not `bullets` or `row_cards`. The test is whether every item is a short *label* (a name) rather than a sentence or a described item. `tag_row` lays 2-16 short labels out as a wrapping row of capsule pills, each label measured with its real per-character width so a CJK/Latin-mixed tag wraps correctly, with an optional `emphasis: "first"` that draws the first tag in the theme accent as the primary one among the rest. Keep `bullets` for a real prose list (items that read as sentences or clauses), and `row_cards`/`icon_cards` for items that each carry their own descriptive text — a tag has none.
34
+
35
+ Fields: `items` (2-16 short strings, each ≤24 chars — a hard cap, because a tag is a label and not a sentence; over it, `validate` points you at `bullets`/`row_cards`), an optional overall `title`, and an optional `emphasis` (`"first"` or `"none"`, default `"none"`). 2 is a hard floor (a single label isn't a row — put it in the heading, a `callout`, or a `verdict_banner`) and 16 is a hard ceiling (past 16 the row reads as an unsorted keyword dump — split into multiple `tag_row` slides or group the tags into labeled sets).
@@ -0,0 +1,40 @@
1
+ ---
2
+ summary: 'skills/pptwise/references/components.md 的中文阅读镜像'
3
+ mirror_of: skills/pptwise/references/components.md
4
+ ---
5
+
6
+ # 组件指南
7
+
8
+ 何时读:碰到形态相近的组件,或要看字段与上下限时。
9
+
10
+ `steps` 和 `flowchart` 是最常见的混用:只要分支路径从不出现,就是 `steps`。`flowchart` 和 `cycle` 是次常见的:这个流程最终走到一个终点,还是转回自己的起点?把一个闭环硬塞进 `flowchart`,那条收尾的回边会被画成一条横跨整张图的迷路线段或大弧线——这不是画图的 bug,是选错了 component;只要最后一个阶段的箭头是指回第一个阶段,就该换成 `cycle`。`roadmap` 和 `gantt` 是再下一个:`roadmap` 把多条工作线分组进泳道,没有共享的数值坐标轴,`gantt` 则把带日期的条形画在一根所有条目共同比对的共享坐标轴上。`pest` 和 `swot` 是再下一个:`pest` 只看外部宏观环境因素(没有内部优势/劣势这条轴),永远是同样命名的四个类别——一份内部对外部的战略评估仍然是 `swot`。`sankey` 和 `flowchart`/funnel `chart` 是再下一个:`sankey` 在分支/汇合的路径上守恒并拆分一个数量(带宽本身就承载意义),`flowchart` 是没有数量含义的决策/流程分支,funnel `chart` 则永远只沿一条线收窄,从不分支也不汇合。`data_table` 和 `chart` 和 `comparison` 是最后一组:受众要逐行读的精确数字用 `data_table`,一眼看出趋势/对比形态的用 `chart`,没有精确数字、只做定性并排属性对比的用 `comparison`。
11
+
12
+ `chart` 内部,子型就是数据的形态。两根轴都是数值量时用 `scatter`(给每个点加可选 `size` 就成了气泡图)。线下方的填充区要读作累积或体量时用 `area`。部分对整体的占比用 `donut`,中心可选把总值放大居中(`center_total: true`)。单个指标对目标的完成度用 `gauge`。`gauge` 和 `kpi_cards` 最要分清:`gauge` 是单个完成度指标,画成一段填充的半环(例如 62% 达标),`kpi_cards` 则是多个各自独立的头条数字并排陈列,所以别在该用 `kpi_cards` 的地方摆一排 gauge。`scatter` 和 `line` 的区别:`scatter` 需要数值 x(两根轴都是真实坐标),x 轴是类目标签的仍然是 `line`。
13
+
14
+ `architecture` 的 `layers` 数组默认从上到下画(`layers[0]` 是最顶层的那条带)——这是自顶向下撰写系统分层(表现层在前、基础设施在后)的自然顺序。如果是一个自底向上的叙事(成熟度阶梯、基础优先的能力模型),就按它自己从低到高的自然顺序撰写,并在 component 上设 `direction: "bottom_up"`,让 `layers[0]` 改画在最底部——不要手动把数组倒过来伪造这个效果,这个字段存在的意义正是让数组始终保持叙事顺序。
15
+
16
+ `swot`/`bmc`/`waterfall`/`gantt`/`pest`/`five_forces`/`heatmap`/`sankey` 是「满幅」(full-body)组件:各自占满整张 slide,且必须是该 slide 唯一的 component——见 `references/density.md`。
17
+
18
+ ### cycle vs. flowchart
19
+
20
+ 两者都是用箭头把一串阶段连起来,区别在于这个流程有没有终点。`flowchart` 面向一个从某处开始、到某处结束的流程,哪怕中途有分支;硬要用它画一个闭环,做法只能是从最后一个节点拉一条边指回第一个节点,而 `flowchart` 的排布引擎并不知道这条边有什么特殊——画出来就是一条横跨整张图的迷路线段或大弧线,读起来像画错了,不像「这个流程会重复」。`cycle` 面向没有终点、总会转回自己起点的流程(PDCA、产品生命周期、飞轮、季节性循环、「设计 → 构建 → 复盘 → 设计」)。判断标准很直接:最后一个阶段的箭头,指向的是一个新东西,还是指回第一个阶段?指回第一个阶段,就用 `cycle`,不用再犹豫。
21
+
22
+ 字段:`items`(3-8 项,每项必填 `label`,可选 `description`),可选的整体 `title`。`cycle` 不接受 `direction` 字段(阶段固定按顺时针排布,`items` 就按这个阅读顺序撰写),也没有中心文字槽——把内容留给阶段本身,别的信息放进 slide 周围的文字里。3 是硬下限(2 个阶段视觉上闭不成一个环,该用 `flowchart` 或 `steps`),8 是硬上限(第 9 个节点会把环挤到 1280x720 slide 上不够清楚的程度,超过就拆成多张 `cycle` slide,不要硬塞进一个环里)。
23
+
24
+ ### 设备样机 vs. 普通图片
25
+
26
+ `device_mockup` 把一份资产框进一个主题化的浏览器窗口或手机机身,而不是一个普通带边框的矩形——它只为一件事存在:一张截图需要被读成「一个真实的产品,正在运行」,而不是「slide 上的一张图」。内容是软件/App/仪表盘的截图,且这一页的论点就是「这个产品是真的、正在正常工作」时用它。除此之外——普通照片、示意图、插画,或者只是顺带用截图说明一个观点而不是断言「这在真实运行」——都用 `image`。把不是产品截图的内容硬套 `device_mockup`,读出来只是个奇怪的装饰边框,不是证据。
27
+
28
+ 字段:`device`(`"browser"` 或 `"phone"`,必填,pptwise 不猜)、`asset_id`(语义同 `image`)、可选 `caption`,以及——仅 `browser` 款——可选的 `url`,渲染为地址栏文字(这是「这是真的在浏览器里跑」这件事上最强的信号)。`phone` 款没有地址栏,`validate` 会硬拒绝在 `phone` 上设置 `url`。屏幕内容永远铺满裁切(cover)——不像 `image` 那样有 `fit` 可选:真实设备的屏幕就是边到边铺满的。故意不提供其它装饰选项——没有倾斜/透视、没有暗色窗框开关、没有多设备并排——窗框配色完全由主题 token 决定。
29
+
30
+ ### 人员卡片 vs. row/icon cards
31
+
32
+ 判据很直接:条目是不是「人」?团队名单、讲者阵容、评委阵容、作者名单,用 `people_cards`:2-12 人的等重卡片网格,每张卡是一个由 `name` 派生的确定性 initials 徽章(不需要照片资源),加姓名和可选的 `role`/`org`。非人条目仍用 `row_cards`/`icon_cards`,哪怕字段形状很像。这两个组件上限都是 6 项,`people_cards` 是 12 项:一份会撑爆 6 上限的人员名单(比如 9 位讲者的大会阵容),就是该换 `people_cards`、而不是硬拆成两页无标签 `row_cards` 的最清楚信号。
33
+
34
+ 字段:`people`(2-12 项,每项必填 `name`,可选 `role`/`org`),可选的整体 `title`。initials 徽章是 `name` 的纯函数:拉丁名取首两词的首字母("Sarah Chen" → "SC"),单个拉丁词取它自己的前两个字母,CJK 名只取首字符,也就是姓("王小明" → "王"),不取两个字。这个组件故意没有照片字段:真有头像照片的场景,`image_grid` 已经够用,`people_cards` 存在的全部理由就是这个零资产依赖的 initials 徽章。2 是硬下限(一个人的简介用不上网格,改用 `callout` 或纯文字),12 是硬上限(更大的名单拆成多张 `people_cards` slide,不要硬塞第 13 张卡进一个网格)。
35
+
36
+ ### 标签行 vs. bullets/卡片
37
+
38
+ 一行短平行标签——技术栈、能力或技能清单、关键词、供应商持有的资质——用 `tag_row`,不是 `bullets` 或 `row_cards`。判据是每一项是不是一个短*标签*(一个名词),而不是一句话或一个带描述的条目。`tag_row` 把 2-16 个短标签排成一行会自动换行的胶囊,每个标签按其真实的逐字符宽度测量,所以 CJK/拉丁混排的标签也能正确换行,可选的 `emphasis: "first"` 把首个标签画成主题 accent 色,作为其余标签中的主标签。真正的正文列表(读起来是句子或从句的条目)仍用 `bullets`,每项自带描述文字的条目用 `row_cards`/`icon_cards`——标签没有描述。
39
+
40
+ 字段:`items`(2-16 个短字符串,每个 ≤24 字符——这是硬上限,因为标签是标签、不是句子;超了 `validate` 会把你指向 `bullets`/`row_cards`),可选的整体 `title`,可选的 `emphasis`(`"first"` 或 `"none"`,默认 `"none"`)。2 是硬下限(单个标签不成行——放进标题、`callout` 或 `verdict_banner`),16 是硬上限(超过 16 个后这行读起来就是一堆没排序的关键词——拆成多张 `tag_row` slide,或把标签分成带小标题的组)。
@@ -0,0 +1,17 @@
1
+ # Density and beat
2
+
3
+ Read this when pacing budgets, `beat`, capacity, or slide `decor` are in play.
4
+
5
+ ### Capacity
6
+
7
+ A slide is a fixed-size canvas. Draft to fit on the first pass: few components per slide, short assertive headings, bullet items within about two lines. Component and bullets budgets scale with the deck's `pacing` axis (tightest for `spacious`, loosest for `dense`) — `validate` reports the exact numbers that applied, not a flat constant. These are warnings, not hard errors — worth fixing for a tighter deck, but they never block `render`. Body text size scales the other way: `spacious` renders the largest body font (32px vs. `balanced`'s 24px and `dense`'s 20px) even though it allows the fewest components, so a `spacious` slide needs fewer and shorter items, not just tighter ones. A bullet item that is long regardless of pacing — long enough to still overflow after shrinking to the render floor — *is* a hard `validate` error, for every bullet style (`default`/`plain`/`divided`/`numbered`/`checklist` alike — it would otherwise lose real text to an ellipsis). Treat "keep bullet items short" as a real constraint regardless of style. When in doubt, split into two slides — writing to fit beats fix-up loops.
8
+
9
+ Eight component types own the whole slide instead of sharing it: `swot`, `bmc`, `waterfall`, `gantt`, `pest`, `five_forces`, `heatmap`, `sankey`. Each must be its slide's only component — `validate` hard-errors on a slide that mixes one in with `bullets` or anything else, it never silently drops the sibling.
10
+
11
+ ### Beat
12
+
13
+ A content page's optional `beat` (`anchor`, `dense`, or `breathing`) is more than a `spec validate` rhythm check now — it also nudges which layout `render` auto-picks for that page: `anchor` leans toward a single bold-statement layout, `dense` leans toward a high-density layout with more visible items, `breathing` leans toward the most spacious single-column layout. It is a soft weight, not a pin — an explicit `layout` still overrides it entirely, and an unset `beat` has zero effect. Declare it deliberately, one value per page based on that page's actual role in the argument (the "big reveal" page is `anchor`, a data-heavy comparison page is `dense`, a breather page between two dense sections is `breathing`), not as a rubber stamp on every page — `spec validate`'s own beat-rotation gate already flags a streak of identical declared beats for strategies that expect variation, and stamping the same value everywhere also just cancels out the layout variety this field exists to add.
14
+
15
+ ### Decor
16
+
17
+ Set slide `decor` only when the user explicitly asks for decorative flourish. Default is none — themes already carry their own motifs.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: 'skills/pptwise/references/density.md 的中文阅读镜像'
3
+ mirror_of: skills/pptwise/references/density.md
4
+ ---
5
+
6
+ # 密度与 beat
7
+
8
+ 何时读:处理 pacing 预算、`beat`、容量、或 slide `decor` 时。
9
+
10
+ ### 容量
11
+
12
+ 一张 slide 是一块固定尺寸的画布。第一遍起草就要考虑装得下:每张 slide 少放几个 component,标题简短有力,bullet 条目控制在约两行以内。component 数和 bullets 预算随这份 deck 的 `pacing` 轴变化(`spacious` 最紧,`dense` 最松)——`validate` 会报出实际生效的具体数值,不是一个写死的常数。这些是警告,不是硬错误——值得为了让 deck 更紧凑而修,但从不拦住 `render`。正文字号则反过来变化:`spacious` 渲染出的正文字号最大(32px,相对 `balanced` 的 24px 和 `dense` 的 20px),即便它允许的 component 数最少——所以一张 `spacious` 的 slide 需要更少、更短的条目,而不只是更紧凑。不论 pacing 是什么,一条长到在渲染安全字号地板下仍然溢出的 bullet 条目,*就是*一条硬 `validate` 错误,五种 bullet 样式(`default`/`plain`/`divided`/`numbered`/`checklist`)一视同仁——否则它会被省略号真的截掉一段真实文字。把「bullet 条目要短」当成一条不分样式都成立的硬约束。拿不准的时候就拆成两张 slide——一遍写对,好过事后反复修补。
13
+
14
+ 有八种 component 类型独占整张 slide,而不是与其他组件共享:`swot`、`bmc`、`waterfall`、`gantt`、`pest`、`five_forces`、`heatmap`、`sankey`。各自必须是所在 slide 唯一的 component——`validate` 会在一张 slide 把其中之一和 `bullets` 或其他任何组件混在一起时硬报错,绝不会静默丢弃那个「陪衬」的 component。
15
+
16
+ ### Beat(节奏标记)
17
+
18
+ 一张 content 页面上可选的 `beat`(`anchor`、`dense` 或 `breathing`)现在不只是 `spec validate` 的节奏检查——它还会影响 `render` 给这一页自动选出哪个 layout:`anchor` 偏向单一的强断言式 layout,`dense` 偏向可见条目更多的高密度 layout,`breathing` 偏向最舒展的单栏 layout。它是一个软权重,不是钉死的选择——显式的 `layout` 依然会完全覆盖它,未设置的 `beat` 则毫无影响。要有意识地声明它,按每一页在论证里的实际角色各给一个值(「重磅揭示」的那页是 `anchor`,数据密集的对比页是 `dense`,两个高密度段落之间的换气页是 `breathing`),而不是每一页都盖同一个章——`spec validate` 自己的 beat 轮换门已经会对期望有变化的 strategy 标出一连串相同 beat 的问题,而且到处盖同一个值本来就会抵消这个字段存在的目的:给 layout 增加变化。
19
+
20
+ ### Decor(装饰)
21
+
22
+ 只有当用户明确要求装饰性点缀时,才设置 slide 的 `decor`。默认不设——theme 本身已经带着自己的视觉母题。