mcpField
D
d2-slides

d2-slides

github.com/mizchi文档处理458381 次安装3 周前更新
代码仓库

关于 d2-slides

d2-slides 是一款技能,可用 Markdown 编写幻灯片,并以 D2 图表构建 HTML。它适合需要可审阅、可重复生成的演示文稿作者,并提供渲染、文本、对比度、图表事实及读回效果检查。

主要功能

  1. 01用 Markdown 编写幻灯片
  2. 02构建 HTML 和打印视图
  3. 03提示正文溢出
  4. 04检查页面完整性与对比度
  5. 05依据事实表验证图表
  6. 06检查幻灯片文本的保真度与顺序

适用场景

  1. 01通过代码差异审阅演示文稿
  2. 02检查幻灯片裁切和溢出
  3. 03依据预期事实验证图表
  4. 04对照幻灯片文本检查读者读回内容

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mizchi/explainer --skill d2-slides

安装后在 Claude Code 中输入/d2-slides确认已加载。

附带文件assets/build-deck.mjsassets/deck-review.mjs

详细介绍

d2-slides

以下路径中:<skill> 是此技能的目录,<skills> 是技能安装所在的目录(在 mizchi/explainer 中为 skills/,在安装了这些技能的项目中为 .claude/skills/)。

一套幻灯片就是文本:正文是 Markdown,每张图都是一个 ```d2 围栏代码块,构建结果完全由文件决定。不需要拖拽,因此可以在差异中审阅幻灯片、在重命名后重新生成,而且——由于输出是 HTML 页面——还可以检查。最后这一点才是关键:幻灯片通常没有失败状态,所以没人会注意到项目符号被画面截掉了一半。

node <skill>/assets/build-deck.mjs deck.md --out built
# ✓ built/index.html: 8 slides, 5 figure(s) laid out by tala, 26 copy lines → built/copy.txt
#   the deck:   built/index.html   → vlmkit check integrity
#   all slides: built/print.html   → vlmkit check copy --manifest, check a11y contrast, print to PDF

需要安装带 TALA 的 d2(d2 layout 必须列出 tala (bundled)——安装方法见 d2-diagram),除此之外无需任何依赖:构建器没有依赖项,幻灯片也不会加载网络资源。

幻灯片格式

---
title: Nothing tells you the picture is wrong
subtitle: two rounds, measured
date: 2026-09-14
---

# Nothing tells you the picture is wrong      ← a lone `#` heading is the title slide

---

## A D2 diagram is a drawing                  ← `##` is a slide heading

- `d2 validate` reads **syntax**              ← bullets; `code`, **bold**, *italic* work
- there is no `--expect`

```d2                                          ← the figure, laid out by TALA
direction: right
write -> check -> facts
```

          ← speaker notes: printed, never on screen
  • 幻灯片之间用一行内容恰好为 --- 的分隔线隔开;围栏代码块中的 --- 不算分隔线。
  • 项目符号加图形会将两者并排显示;只有图形时会占据整个画布;只有标题时则是一张标题幻灯片。> line 是引用语句,其他语言的围栏代码块仍会作为代码块处理。
  • 每张幻灯片都是固定的 1280×720 画面,会缩放以适应显示它的屏幕。在笔记本电脑和投影仪上显示时,内容不会重新排布,这也是为什么第 4 张幻灯片的屏幕截图在任何地方看起来都一样。

工作流程

1. write deck.md                                     prose in Markdown, every figure a ```d2 fence
2. node …/build-deck.mjs deck.md --out built         fails on the first figure that does not compile,
                                                     naming the slide; warns when a slide's prose
                                                     overflows its frame's character budget
3. vlmkit check integrity built/index.html            a slide that breaks, clips, collides or paints nothing
4. vlmkit check integrity built/print.html --viewports 1280
                                                     every slide at once — this is what catches the
                                                     bullet the frame cut off
5. vlmkit check copy built/print.html --manifest built/copy.txt --allow-invisible unknown
                                                     every line of the deck's own text still on a slide
                                                     (add --forbid stale.txt when you are editing a deck:
                                                     the claim you removed must really be gone)
6. vlmkit check a11y contrast built/print.html        readable from the back of the room
7. node …/d2-facts.mjs --from-svg built/slide-NN.svg --expect slide-NN.facts.json
                                                     what each figure DRAWS, for every figure that has
                                                     facts it must not get wrong — steps 3-6 are blind to it
8. node …/deck-review.mjs built --out review           shoot each slide, hand the PNGs to a reader
   node …/deck-review.mjs built --answers a.json      score what it read back: fidelity, order, SPLITs
9. read built/index.html in a browser, or the PNG of a slide; fix the Markdown; go to 2

第 7 步不是可有可无的装饰,而且四项页面检查也不涵盖这一步。 它们检查的是页面:一张图即便能编译、能绘制,却画错了内容,也能通过全部四项检查。检查器会读取构建过程已生成的幻灯片 SVG,因此不需要 d2(它读取的是渲染结果),每张图只需运行一条命令:

node <skills>/d2-diagram/assets/d2-facts.mjs --from-svg built/slide-04.svg \
  --expect slide-04.facts.json     # exits 1 and names the box or edge that is wrong

同样的方法还能相互约束两张图,这是其他方法做不到的:如果第 2 张和第 3 张都声称 ledger 是写入方,就让两张 SVG 都符合一份事实表({"boxes": ["checkout","ledger"], "deps": ["checkout->ledger"], "forbidden": ["checkout->orders"]})。更新了一张图,却忘了另一张,这份事实表就会因你漏掉的那张图而检查失败。事实表的架构属于 d2-diagram;设置 exhaustive: true 后,多余的方框就会报错,而不是被忽略。

第 8 步:总得有人读一遍

第 3–7 步处理的是页面和图形的几何布局。它们都不会像观众那样阅读幻灯片,而这种差距并非纸上谈兵:在 v1/v2 轮次中,有两个缺陷进入了交付的演示文稿——一个句子被渲染成两个文本块,还有一个片段出现在它所属的项目符号上方——而且所有四道检查都通过了,因为每个片段都是可见文本,也没有任何内容溢出。

node <skill>/assets/deck-review.mjs built --out review
#   → review/slide-NN.png, one per slide, plus review-brief.md
#   hand that directory to a vision model or an agent; it answers JSON
node <skill>/assets/deck-review.mjs built --answers review/answers.json
#   read 19/21, invented 6, fidelity 0.70, order 1.00
#   ⚠ SPLIT: "…it calls ledger, which is now the only writer to orders."
#     every word is on the slide, the sentence is not — read back as 2 blocks

评分是确定性的,依据的是构建时写入的 slides.json:每张幻灯片按阅读顺序排列的文本(copy.txt 不是按此顺序——清单是一个集合,而且项目符号列在段落之前,而页面上段落先于项目符号显示),再加上图形绘制的文字。

需要关注两个数字和一行提示:

  • 保真度——读到的原文行数 /(原文行数 + 虚构内容)。1.00 表示“与幻灯片一致,没有多余内容”。在 v3 中测得:两个读取器对无缺陷演示文稿的保真度都是 1.00;同一演示文稿由一个拆分了项目符号的构建器生成时,保真度为 0.70。
  • 顺序——阅读顺序与原文顺序不一致的文本行对数。它用于检测真正的顺序错乱幻灯片;请注意,它未能发现 v3 的拆分,因为片段仍保持着相对于原文的顺序。保真度和 SPLIT 行发现了这个问题。
  • ⚠ SPLIT——某一原文行没有任何单条读取行能覆盖,但其中的词语都分散在两条或更多读取行中。这表示一个句子被渲染成了两个文本块。无论读取器是否有对应的词来描述问题,工具都会指出它:某个 v3 读取器把同一个缺陷称为“右边缘被裁切”——幻灯片没错,但判断的机制错了——诊断结果仍然识别出了这个问题。

--sheet <other-build>/slides.json 会将一次渲染结果与另一个构建版本的原文进行评分,用于将前后版本与同一份基准原文进行比较。

请诚实看待其局限性。读取器没有转录任何图形标签,并不能证明标签难以辨认,只能说明它读取了正文——figures: k of n 这一行是提示,不是结论。读取结果是一种测量,进程退出码为 0;如何处理 0.7,由作者决定。读取器偶尔也会报告并非真正原因的机制,因此在相信它给出的原因之前,先阅读它指出的幻灯片。

完成条件

  • 构建进程以 0 退出,且没有溢出警告。
  • check integrity 对 index.html(全部三个视口)和 1280 宽度下的 print.html 均报告无失败。warn 不算失败,命令仍会以 0 退出——在花一轮时间追查之前,请先看下面关于图形内部结构的说明。
  • check copy --manifest 报告没有缺失内容(如果你在编辑现有演示文稿,也没有被禁止的内容)。
  • check a11y contrast 报告无失败,且读取数量足以表明每张幻灯片都已读取。数量取决于内容——内容简短的演示文稿每张约有 7 个元素,内容密集的演示文稿每张约有 17 个——因此数量本身不是检查标准。也要对 index.html 运行检查:它一次只显示一张幻灯片,因此结果会是个位数。打印视图 ≫ 演示文稿视图才是检查重点。
  • 每张包含不得出错事实的图形,都通过了 d2-facts --expect 检查。
  • 有人读过了。 你亲自翻看了整份演示文稿,或有读取器使用 deck-review 进行了评分——保真度接近 1.00,且没有 SPLIT。只有这一步能发现一个句子被拆成两个文本块的问题。

CI 中的同一循环

每次修改技能、示例或它所使用的三个门禁时,此仓库都会让自己的完整示例走一遍该循环,因此上述说法是经过测量验证的,而非仅仅断言。分为两部分,因为它们需要不同的机器:

  • tests/d2/d2-slides.test.mjs(在常规测试套件中运行,不需要 d2,也不需要浏览器)——绘制图形前构建器所做的决策:幻灯片在哪里结束(代码围栏内的 --- 不是分隔符)、幻灯片采用哪种布局、以渲染后文本作为清单、指出幻灯片名称的溢出警告、未能编译的图形会导致构建失败,以及 examples/d2-slides/built/ 仍与其 deck.md 一致。D2 指向一个会回显固定 SVG 的存根——构建器已经会读取该变量。
  • .github/workflows/d2-slides.yml(固定为 v0.9.0 的真实 d2、真实 Chromium)——在全新构建上运行上述四个门禁,另加与已提交构建的字节级比较,以及两个负向对照:清单中一行未在演示文稿中出现的文本必须报告为 copy-missing,缩短到 260px 的画框必须报告为 clipped-content。如果测试套件只看到通过,就无法区分真正起作用的门禁和永远放行的门禁。

编辑示例的 deck.md 后,使用 npm run deck:example 重新生成已提交的示例——字节级比较可以防止已提交的构建逐渐偏离事实。

门禁在此技能自己的演示文稿中发现的问题

以下都是模板或示例中的真实缺陷,由门禁发现、修复并重新测量。这正是上述循环采用该顺序的原因。

  • 手机上什么都看不到。 在 375px 下运行 check integrity:"DOM 中有 4 个文本块,但几乎没有内容被绘制出来(墨迹比例为 0.05%)"。将 1280px 的舞台作为超大网格项居中,会导致中间部分位于屏幕外;现在通过变换来居中舞台。
  • 每张幻灯片都被裁掉了 114px。 固定盒子带内边距时,百分比高度并不等于盒子的高度:画框现在是弹性列布局,正文是弹性子项。对于文字明明完全位于画框内的幻灯片,门禁却报告为 clipped-content。
  • 较长的项目符号列表上下都被截断。 分栏布局设置 align-items: center 时,高度超过画框的正文会向上下溢出,而文案门禁会把这些行视为用户看不到的内容。现在溢出会向下延伸,便于测量;如果图形旁的正文超过约 430 个字符,构建器会发出警告。
  • 清单与渲染结果不一致。 清单根据渲染后的标记生成,而非剥除标记后的 Markdown:包含一行 ```d2 strips to something the page never says.

What the copy manifest covers

copy.txt is generated from the Markdown prose only — each slide's heading, bullets, pull quotes and paragraphs, as the page renders them (markers gone, entities decoded). Three things are therefore not in it:

  • A figure's labels. Text inside a ```d2 fence is drawn as SVG, and the manifest never contains it. So a string the deck must be seen to say belongs in prose; if it only appears in a figure, the copy gate cannot vouch for it — hold the figure to a fact sheet instead (step 7).
  • Speaker notes. `` is printed and never on screen, and it is not in the manifest either.
  • The chrome. The footer's deck title and page number are not manifest lines.

For a reader, the build also writes slides.json: per slide, its heading, its text in reading order, its figures and the words those figures draw. That is the sheet deck-review scores against, and the reason it is a separate file from copy.txt is that a manifest cannot express order.

A bullet, quote or paragraph wrapped over several lines is rejoined into one line, as Markdown does, so wrapping at 80 columns is safe and one sentence is one manifest line. A blank line ends the block: an indented line after one is a paragraph of its own.

Failure modes

  • ✗ slide 6-1: the d2 figure does not compile with reserved keywords are prohibited in edges → an id collided with a D2 keyword. width, height, label, style, shape, icon, near, top, left, direction, class, link, constraint, layers, steps and scenarios are the ones that bite; rename the box (cols, verdict) and keep the words in its label.
  • copy-invisible (reason: unknown) on a line that is plainly on the slide → a vlmkit gate limitation, not your deck: a manifest line whose rendered text is assembled from several inline children can read as invisible. Reproduced minimally in examples/d2-slides/README.md; three bullets carrying <code> and <strong> fail, the same three lines without markup pass. Run with --allow-invisible unknown (every accepted line is listed, so the suppression stays auditable) and check the missing count, which is the part that matters.
  • check a11y contrast inspected only four elements → you pointed it at index.html. One slide is on screen at a time; print.html is the stacked view. Do not read the absolute count as a threshold: it tracks how many text nodes the deck has, so a terse deck legitimately sits near 7 a slide. The comparison between the two views is the signal.
  • near-misalignment (a warn) on a base64-looking selector like g.KGdhdGV3YXkgLSZndDsgY2hlY2tvdXQpWzBd → that is inside a figure's SVG: the gate has found two of TALA's own edge-label groups sitting 5.5px apart, which is the layout engine's business and not the deck's. It is a warn, so the verdict reads NO DEFECTS, n WARN and the command exits 0 — that is done, leave it. There is no stable way to exempt it either: --allow matches the finding's own selector by substring, and d2 derives that class from the edge's text, so the string changes with the label. If a run has to be silent, --rule near-misalignment=off is the only lever, and it gives up the rule for the slide chrome too. Whether it fires at all depends on the layout, not on having labelled edges: the same deck's figures can be CLEAN after an edit.
  • page-overflow-x on print.html at 768 or 375 → expected: the print view is a column of 1280px frames. Check it at --viewports 1280.
  • A figure is enormous or unreadable → it is a D2 diagram like any other: take it to d2-diagram, where the width levers, the fact check and the terminal render live. A slide figure has about 700×560 to live in.
  • CJK labels render correctly in SVG, so a Japanese deck is fine; the terminal-render caveats in d2-diagram do not apply here.

Deliver

  • The deck: built/index.html, opened in a browser. Arrows and space move, o is the overview grid, p prints, f is fullscreen, and the URL carries #/4, so one slide can be linked.
  • A PDF: open print.html and print it, or index.html and press p. Speaker notes appear in print and never on screen.
  • In a repo: commit deck.md and the figures' .svg; the HTML is a build output. examples/d2-slides/ is the worked example — its deck, its build, and the gate runs above.
  • One slide as an image: the figure is already built/slide-NN.svg.

评论

支持 Markdown