d2-slides

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

d2-slides

d2-slides

Paths below:
<skill>
is this skill's directory and
<skills>
the directory the skills are installed in (
skills/
in mizchi/explainer,
.claude/skills/
in a project that installed them).
A deck is text: the prose is Markdown, every figure is a ```d2 fence, and the build is a pure function of the file. Nothing is dragged, so a slide can be reviewed in a diff, regenerated after a rename, and — because the output is an HTML page — checked. That last part is the point: a slide deck normally has no failing state, so nobody notices the bullet the frame cut in half.
bash
node <skill>/assets/build-deck.mjs deck.md --out built
以下路径中:
<skill>
表示此 skill 的目录,
<skills>
表示 skill 的安装目录 (
skills/
位于 mizchi/explainer 中,
.claude/skills/
位于安装了这些 skill 的项目中)。
演示文稿就是文本:正文使用 Markdown,每个图形都是一个 ```d2 fence,build 是该文件的纯函数。无需拖拽,因此幻灯片可以在 diff 中审阅,重命名后也能重新生成;而且由于 输出是一个 HTML 页面,所以可以检查。最后这一点才是关键:普通 slide deck 通常没有失败状态,因此没人会注意到被 frame 切成两半的项目符号。
bash
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

✓ 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

the deck: built/index.html → vlmkit check integrity

all slides: built/print.html → vlmkit check copy --manifest, check a11y contrast, print to PDF

all slides: built/print.html → vlmkit check copy --manifest, check a11y contrast, print to PDF


Requires `d2` with TALA (`d2 layout` must list `tala (bundled)` — see
`d2-diagram` for installing it) and nothing else: the builder has no
dependencies and the deck loads no network resource.

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

The deck format

Deck 格式

markdown
---
title: Nothing tells you the picture is wrong
subtitle: two rounds, measured
date: 2026-09-14
---
markdown
---
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

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



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

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

  • d2 validate
    reads syntax ← bullets;
    code
    , bold, italic work
  • there is no
    --expect
d2
direction: right
write -> check -> facts
<!-- notes: what to say out loud --> ← speaker notes: printed, never on screen

- Slides are separated by a line of exactly `---`; a `---` inside a fence is not
  a separator.
- **Bullets plus a figure** lays them side by side; **a figure alone** gets the
  whole stage; a heading alone is a title slide. `> line` is a pull quote, and a
  fence in any other language stays a code block.
- Each slide is a fixed **1280×720 frame**, scaled to whatever screen it is
  shown on. Nothing reflows between a laptop and a projector, which is also why
  a screenshot of slide 4 is the same picture everywhere.
  • d2 validate
    reads syntax ← bullets;
    code
    , bold, italic work
  • there is no
    --expect
d2
direction: right
write -> check -> facts
<!-- notes: what to say out loud --> ← speaker notes: printed, never on screen

- 使用恰好一行 `---` 分隔幻灯片;fence 内的 `---` 不构成分隔符。
- **项目符号加图形**会将二者并排布局;**仅图形**会占据整个 stage;**仅标题**会成为标题幻灯片。`> line` 是 pull quote,其他语言的 fence 会保留为 code block。
- 每张幻灯片都是固定的 **1280×720 frame**,会缩放到实际显示的屏幕。内容不会在笔记本电脑和投影仪之间重新排布,因此第 4 页幻灯片的截图在任何设备上都是同一幅画面。

The loop

流程

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
Step 7 is not optional decoration, and the four page gates do not cover it. They read the page: a figure that compiles and draws the wrong thing passes all four. The checker takes the slide SVGs the build already writes and needs no
d2
(it reads the render), so this costs one command per figure:
bash
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
The same trick pins two figures to each other, which nothing else does: when slides 2 and 3 both claim
ledger
is the writer, hold both SVGs to one sheet (
{"boxes": ["checkout","ledger"], "deps": ["checkout->ledger"], "forbidden": ["checkout->orders"]}
). Update one figure and forget the other and that sheet fails on the one you missed. The fact-sheet schema is
d2-diagram
's;
exhaustive: true
is what makes an extra box an error rather than a shrug.
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
第 7 步不是可有可无的装饰,四个页面 gates 也没有覆盖它。 Gates 读取的是页面:一个能够成功编译却画错内容的图形会通过全部四项检查。Checker 使用 build 已经写出的 slide SVG,不需要
d2
(它直接读取渲染结果),因此每个图形只需执行一条命令:
bash
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遵循同一份 sheet(
{"boxes": ["checkout","ledger"], "deps": ["checkout->ledger"], "forbidden": ["checkout->orders"]}
)。只更新了一个图形却忘记另一个时,这份 sheet 会在你遗漏的那个图形上失败。fact-sheet 的 schema 与
d2-diagram
相同;
exhaustive: true
会将额外的 box 视为错误,而不是予以放过。

Step 8: somebody has to read it

第 8 步:必须有人读回内容

Steps 3-7 are the page and the figures' geometry. None of them reads the slide the way a viewer does, and that gap is not theoretical: in the v1/v2 rounds two defects reached a delivered deck — a sentence rendered as two blocks, and a fragment sitting above its own bullet — with all four gates green, because every fragment was visible text and nothing overflowed.
bash
node <skill>/assets/deck-review.mjs built --out review
步骤 3–7 负责页面以及图形的几何结构。它们都不会像观看者那样阅读幻灯片,而这种差距并非理论问题:在 v1/v2 轮次中,两个缺陷进入了已交付的 deck——一个句子被渲染为两个块,以及一个片段出现在其所属项目符号上方——而且四项 gates 全绿,因为每个片段都是可见文本,也没有内容溢出。
bash
node <skill>/assets/deck-review.mjs built --out review

→ review/slide-NN.png, one per slide, plus review-brief.md

→ review/slide-NN.png, one per slide, plus review-brief.md

hand that directory to a vision model or an agent; it answers JSON

hand that directory to a vision model or an agent; it answers JSON

node <skill>/assets/deck-review.mjs built --answers review/answers.json
node <skill>/assets/deck-review.mjs built --answers review/answers.json

read 19/21, invented 6, fidelity 0.70, order 1.00

read 19/21, invented 6, fidelity 0.70, order 1.00

⚠ SPLIT: "…it calls ledger, which is now the only writer to orders."

⚠ 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

every word is on the slide, the sentence is not — read back as 2 blocks


The scoring is deterministic, against the `slides.json` the build writes: each
slide's text **in reading order** (which `copy.txt` is not — the manifest is a
set, and it lists bullets before paragraphs while the page renders paragraphs
first) plus the words its figures draw.

Two numbers and one line to read:

- **fidelity** — sheet lines read / (sheet lines + invented). 1.00 is "the
  slide, nothing more". Measured in v3: 1.00 on a clean deck from both readers,
  0.70 on the same deck built by a builder that split its bullets.
- **order** — pairs of read lines whose order disagrees with the sheet. It is
  for a genuinely reordered slide; be aware it did **not** catch v3's split,
  because the fragments still appeared in the sheet's relative order. Fidelity
  and the SPLIT line caught that.
- **⚠ SPLIT** — a sheet line no single read line covers, whose words are all
  present across two or more. That is one sentence rendered as two blocks, and
  the tool names it whether or not the reader had the word for it: one v3
  reader called the same defect "clipped at the right edge" — right slides,
  wrong mechanism — and the diagnosis came out anyway.

`--sheet <other-build>/slides.json` scores one render against another build's
sheet, which is how a before/after pair is compared against one ground truth.

Honest limits. A reader that transcribes none of the figures' labels is not
evidence they are illegible, only that it read prose — the `figures: k of n`
line is a hint, not a verdict. A reading is a measurement and exits 0; what to
do about a 0.7 is the author's call. And a reader will occasionally report a
mechanism that is not the real one, so read the slide it names before believing
the reason it gives.

评分是确定性的,以 build 写出的 `slides.json` 为基准:每张幻灯片按**阅读顺序**排列的文本(`copy.txt` 并非如此——manifest 是一个集合,并且会先列出项目符号再列出段落,而页面实际渲染时先显示段落),再加上图形绘制的文字。

两个数值和一行需要关注的结果:

- **fidelity**——读出的 sheet 行数 /(sheet 行数 + 虚构行数)。1.00 表示“就是幻灯片本身,没有多出内容”。在 v3 中实测:两位 reader 对一份干净 deck 都给出 1.00,而由会拆分项目符号的 builder 构建同一份 deck 时则给出 0.70。
- **order**——读出的行对中,顺序与 sheet 不一致的行对数量。它用于发现真正被重新排序的幻灯片;要注意,它**没有**发现 v3 的 SPLIT 缺陷,因为这些片段在 sheet 中的相对顺序仍然保持不变。fidelity 和 SPLIT 行发现了该问题。
- **⚠ SPLIT**——某条 sheet 行没有任何单条读出行完整覆盖,但其所有词语分散在两条或更多读出行中。这意味着一个句子被渲染成多个块;无论 reader 是否使用 SPLIT 这个术语,工具都会指出它:一个 v3 reader 将同一缺陷称为“在右边缘被裁切”——位置判断正确,但机制判断错误——最终诊断仍然准确。

`--sheet <other-build>/slides.json` 会将一次渲染与另一份 build 的 sheet 进行评分比较,这就是如何用同一份 ground truth 对比修改前后的两个版本。

客观限制。reader 没有转录任何图形标签,并不能证明标签难以辨认,只能说明它只读取了正文;`figures: k of n` 行只是一个提示,并非结论。一次读取是一项测量,退出码为 0;0.7 该如何处理由作者决定。此外,reader 偶尔会报告错误的机制,因此应先查看它指出的幻灯片,再相信其给出的原因。

Done condition

完成条件

  • The build exits 0 with no overflow warning.
  • check integrity
    reports no failures on
    index.html
    (all three viewports) and on
    print.html
    at 1280. A
    warn
    is not a failure and the command still exits 0 — see the figure-internals note below before you spend a round chasing one.
  • check copy --manifest
    reports nothing missing (and nothing forbidden, if you are editing an existing deck).
  • check a11y contrast
    reports no failure, over a count that means every slide was read. The count is content-dependent — a terse deck runs about 7 elements a slide, a dense one about 17 — so the number is not the test. Run it against
    index.html
    too: that shows one slide at a time, so it comes back in single digits. Print view ≫ deck view is the check.
  • Every figure with facts it must not get wrong passed
    d2-facts --expect
    .
  • Somebody read it back. You pressed through the deck yourself, or a reader scored it with
    deck-review
    — fidelity near 1.00 and no
    SPLIT
    . This is the only step that sees a sentence broken into two blocks.
  • build 以 0 退出,且没有 overflow warning。
  • check integrity
    报告
    index.html
    (全部三个 viewports)和 1280 下的
    print.html
    没有 failures。
    warn
    不算 failure,命令仍以 0 退出——在花时间追查某个 warn 之前,请先参阅下方 figure-internals 说明。
  • check copy --manifest
    报告没有 missing(如果是编辑现有 deck,也应没有 forbidden)。
  • check a11y contrast
    报告没有 failure,且检查数量意味着每一张幻灯片都被读取。该数量取决于内容——简洁的 deck 每页约有 7 个元素,密集的 deck 每页约有 17 个——因此数量本身不是判断标准。也要对
    index.html
    运行检查:它一次只显示一张幻灯片,所以结果是个位数。Print view ≫ deck view 才是需要关注的检查结果。
  • 每个包含必须保持正确事实的图形都通过了
    d2-facts --expect
    。
  • **有人读回了内容。**你已逐页浏览过该 deck,或 reader 已使用
    deck-review
    评分——fidelity 接近 1.00,且没有
    SPLIT
    。这是唯一能够发现一个句子被拆成两个块的步骤。

The same loop, in CI

在 CI 中运行同一流程

This repo runs its own worked example through that loop on every change to the skill, the example or the three gates it uses, so the claim above is measured rather than asserted. Two halves, because they need different machines:
  • tests/d2/d2-slides.test.mjs
    (in the ordinary suite, no
    d2
    , no browser) — the builder's decisions before a figure is drawn: where a slide ends (
    ---
    inside a fence is not a separator), which layout a slide gets, the manifest as the rendered text, the overflow warning naming the slide, a figure that does not compile failing the build, and
    examples/d2-slides/built/
    still matching its
    deck.md
    .
    D2
    points at a stub that echoes a fixed SVG — the builder already reads that variable.
  • .github/workflows/d2-slides.yml
    (real
    d2
    pinned at
    v0.9.0
    , real Chromium) — the four gate runs above on a fresh build, plus a byte comparison against the committed build, plus two negative controls: a manifest line the deck does not say must be reported
    copy-missing
    , and a frame shortened to 260px must be reported
    clipped-content
    . A suite that only ever sees passes cannot tell a working gate from a gate that always passes.
Regenerate the committed example with
npm run deck:example
after editing its
deck.md
— the byte comparison is what keeps a committed build from drifting into a lie.
这个 repo 会在 skill、示例或其使用的三个 gates 每次发生变化时,让自己的 worked example 走完上述流程,因此上面的说法是实测得出的,而不是凭空断言。分为两部分,因为它们需要不同的机器:
  • tests/d2/d2-slides.test.mjs
    (位于普通测试套件中,不需要
    d2
    ,也不需要 browser)——覆盖图形绘制前 builder 的各项决策:幻灯片在哪里结束(fence 内的
    ---
    不是分隔符)、幻灯片采用哪种布局、作为已渲染文本的 manifest、指出幻灯片的 overflow warning、无法编译的图形导致 build 失败,以及
    examples/d2-slides/built/
    是否仍与
    deck.md
    一致。
    D2
    指向一个回显固定 SVG 的 stub;builder 已经会读取该变量。
  • .github/workflows/d2-slides.yml
    (真实的
    d2
    固定为
    v0.9.0
    ,使用真实 Chromium)——在新 build 上运行上述四项 gate,并与已提交的 build 做逐字节比较;另外包含两个 negative controls:deck 未提及的 manifest 行必须报告为
    copy-missing
    ,缩短到 260px 的 frame 必须报告为
    clipped-content
    。一个只见过通过结果的测试套件,无法区分正常工作的 gate 与永远通过的 gate。
编辑
deck.md
后,运行
npm run deck:example
重新生成已提交的示例——逐字节比较可以防止已提交的 build 偏离事实。

What the gates found in this skill's own deck

Gates 在此 skill 自身 deck 中发现的问题

Everything below was a real defect in the template or the example, caught by a gate, fixed, and re-measured. It is why the loop above is in that order.
  • A phone showed nothing.
    check integrity
    at 375px: "the DOM holds 4 text blocks but almost nothing painted (ink ratio 0.05%)". Centring a 1280px stage as an over-sized grid item leaves the middle off-screen; the stage is centred by transform now.
  • Every slide clipped 114px. A percentage height inside a padded fixed box is not the box: the frame is a flex column and the body the flexible child. The gate reported it as
    clipped-content
    on slides whose text was well inside the frame.
  • Long bullets were cut top and bottom. With
    align-items: center
    on the split layout, prose taller than the frame overflows both ways and the copy gate reads those lines as copy a user cannot see. Overflow goes downward now, where it is measurable, and the builder warns above ~430 characters of prose beside a figure.
  • The manifest did not match the render. It is generated from the rendered markup, not from the Markdown with its markers stripped: a line containing
    ```d2
    strips to something the page never says.
以下是此 skill 自身 deck 中 gates 实际发现的问题。所有问题都曾是模板或示例中的真实缺陷,由 gate 捕获、修复并重新测量。这正是上述流程采用该顺序的原因。
  • 手机屏幕没有显示任何内容。
    check integrity
    在 375px 下提示:“DOM 中有 4 个文本块,但几乎没有内容被绘制(ink ratio 0.05%)”。将 1280px stage 居中时,如果把它作为一个超尺寸的 grid item,中间部分会移出屏幕;现在改用 transform 对 stage 居中。
  • **每张幻灯片都裁掉了 114px。**带 padding 的固定 box 内的百分比 height 并不等于该 box 本身:frame 是 flex column,而 body 是其中的 flex child。Gate 将那些文本明明位于 frame 内部的幻灯片报告为
    clipped-content
    。
  • **长项目符号的顶部和底部被裁掉。**在 split layout 中使用
    align-items: center
    时,高于 frame 的正文会同时向上和向下溢出,copy gate 会将这些行识别为用户不可见的 copy。现在溢出改为向下延伸,因此可以被测量;builder 会在图形旁正文超过约 430 个字符时发出 warning。
  • **Manifest 与渲染结果不匹配。**它根据已渲染的 markup 生成,而不是通过移除 Markdown 标记后得到:包含
    ```d2
    的行去除标记后会变成页面从未显示过的内容。

What the copy manifest covers

Copy manifest 的覆盖范围

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.
    <!-- 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.
copy.txt
根据仅 Markdown 正文生成——即每张幻灯片的标题、项目符号、pull quote 和段落,与页面渲染结果一致(标记已移除,实体已解码)。因此有三类内容不在其中:
  • **图形的标签。**位于 ```d2 fence 内的文本会绘制为 SVG,manifest 中永远不会包含它。因此,如果 deck 必须明确说出某个字符串,应将其放入正文;如果它只出现在图形中,copy gate 无法为其提供保证——应改用 fact sheet 约束该图形(步骤 7)。
  • Speaker notes。
    <!-- notes: … -->
    会被打印但不会显示在屏幕上,也不在 manifest 中。
  • **页面框架。**footer 中的 deck 标题和页码不属于 manifest 行。
对于 reader,build 还会写出
slides.json
:其中按阅读顺序记录每张幻灯片的标题、文本、图形以及这些图形绘制的文字。它是
deck-review
评分的基准 sheet,也是它与
copy.txt
分开存储的原因——manifest 无法表达顺序。
跨多行换行的项目符号、引用或段落会像 Markdown 一样重新合并为一行,因此按 80 列换行是安全的,一句话对应一条 manifest 行。空行表示一个 block 结束:空行后的缩进行会构成独立段落。

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.
  • ✗ slide 6-1: the d2 figure does not compile
    且提示
    reserved keywords are prohibited in edges
    → 某个 id 与 D2 keyword 冲突。容易出问题的包括
    width
    、
    height
    、
    label
    、
    style
    、
    shape
    、
    icon
    、
    near
    、
    top
    、
    left
    、
    direction
    、
    class
    、
    link
    、
    constraint
    、
    layers
    、
    steps
    和
    scenarios
    ;请重命名 box(
    cols
    、
    verdict
    ),并将这些词保留在 label 中。
  • 某行明明在幻灯片上,却出现
    copy-invisible (reason: unknown)
    → 这是 vlmkit gate 的限制,而不是 deck 的问题:一条渲染文本由多个 inline children 拼接而成的 manifest line,可能会被判定为不可见。已在
    examples/d2-slides/README.md
    中最小化复现;三条包含
    <code>
    和
    <strong>
    的项目符号会失败,去掉 markup 后同样的三行会通过。运行
    --allow-invisible unknown
    (每个被接受的行都会列出,因此这种抑制仍可审计),并检查
    missing
    count,后者才是关键。
  • check a11y contrast
    只检查了四个元素
    → 你检查的是
    index.html
    。该视图一次只显示一张幻灯片;
    print.html
    才是堆叠视图。不要把绝对数量当作阈值:它会随 deck 的文本节点数变化,因此简洁的 deck 每页接近 7 个元素是合理的。两个视图之间的对比才是信号。
  • near-misalignment
    (一个 warn)出现在类似
    g.KGdhdGV3YXkgLSZndDsgY2hlY2tvdXQpWzBd
    的 base64 风格 selector 上
    → 该 selector 位于某个图形的 SVG 内部:gate 发现 TALA 自身的两个 edge-label group 相距 5.5px,这属于 layout engine 的职责范围,而不是 deck 的问题。它是
    warn
    ,因此 verdict 会显示
    NO DEFECTS, n WARN
    ,命令以 0 退出——这就完成了,无需处理。也没有稳定的方式将其豁免:
    --allow
    通过子字符串匹配 finding 自身的 selector,而 d2 会根据 edge 的文本派生该 class,因此该字符串会随 label 改变。如果某次运行必须完全静默,
    --rule near-misalignment=off
    是唯一的手段,但它也会对 slide chrome 关闭该规则。该问题是否出现取决于 layout,而不是是否存在带 label 的 edge:同一 deck 的图形在一次编辑后可能变为 CLEAN。
  • print.html
    在 768 或 375 下出现
    page-overflow-x
    → 属于预期情况:print view 是一列 1280px frame。请在
    --viewports 1280
    下检查。
  • 图形过大或无法辨认 → 它与任何其他图形一样,都是 D2 diagram:请转到
    d2-diagram
    ,在那里处理宽度调节、fact check 和 terminal render。slide figure 可用空间约为 700×560。
  • CJK 标签可正确渲染为 SVG,因此日文 deck 没有问题;这里不适用
    d2-diagram
    中关于 terminal render 的注意事项。

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
    .
  • Deck:
    built/index.html
    ,在 browser 中打开。方向键和空格用于翻页,
    o
    打开概览网格,
    p
    打印,
    f
    进入全屏,URL 携带
    #/4
    ,因此可以链接到单张幻灯片。
  • **PDF:**打开
    print.html
    后打印,或打开
    index.html
    后按
    p
    。Speaker notes 会出现在打印稿中,不会显示在屏幕上。
  • **在 repo 中:**提交
    deck.md
    和各图形的
    .svg
    ;HTML 是 build output。
    examples/d2-slides/
    是 worked example——包含其 deck、build 以及上述 gate 运行结果。
  • **单张幻灯片图像:**图形已经生成在
    built/slide-NN.svg
    中。