修改一场讲演#
一份 deck 起步很少昂贵。它的代价出现在第二十次修改上 —— 讲演前一晚,评审说结果页还要再加一个指标,而且章节顺序变了。
下面每一处修改都是对配方 deck 的真实改动,以 diff 呈现,并按行数计价。整组改动是一次性全部应用的,结果通过了 frameseq check,没有一处需要事后返工。
换掉整套视觉#
date: "2026",
- theme: "minimal-academic",
+ theme: "paper",
});
一行。 没有任何一页设置字体、颜色或间距,所以每一页都跟着变。这就是应该用 lead()、caption() 这类文字角色、而不是 .size(28).color("#666") 的理由:角色能活过换主题,字面值不能。
把一页拆成两页#
note("Give the audience the problem before the method.");
+
+slide("What a fixed route costs");
text("Measurements in this deck are illustrative.").caption();
+bullets("Easy inputs pay the full round trip", "Hard inputs get no extra budget");
拆开一行,填充新页两行。 一页拥有写在它后面的一切,所以新的 slide() 调用会把余下的内容整体搬到新页,而不用碰它们。没有嵌套,也就没有需要重新缩进或拆包的东西。
移动一页是同一性质的反向操作:把从 slide() 到下一个 slide() 之间的整块剪下来,粘到别处。没有需要配对的包装结构。
把单栏变成对比#
-slide("What changed").notes("Pause after each step; …");
+slide("What changed").split("50:50").notes("Pause after each step; …");
steps(
"Latency fell by 65% for routed inputs",
"Accuracy moved by less than one point",
"The threshold, not the model, carries the trade-off",
);
+
+right();
+text("Still open").lead();
+bullets("Thresholds drift across datasets", "Cold caches lose the latency win");
一个词加一个新区域。 原有内容原地不动,自动成为左栏;right() 开启第二栏。之后想改比例,改一个数字。
给指标行加一项#
gridSection(
- 3,
+ 4,
metric("41 ms", "Median latency"),
metric("72%", "Inputs kept local"),
metric("−0.6 pt", "Accuracy change"),
+ metric("1.8×", "Battery life"),
).gap(20);
两行。 列数和那一项;网格自己重新分配间距。没有任何一项带宽度,所以没有需要重算的东西。
整张图挪位#
-at("flow").row().gap(90).anchor("center");
+at("flow").row().gap(90).anchor("left", 90);
一个词。 框本身没有坐标,所以整张图作为一个整体移动,连线跟着走。改 gap(90) 同理,会重排所有框和它们的连线。
给图加一个节点#
rect("Cloud").as("cloud").width(200).height(110);
+rect("Cache").as("cache").width(200).height(110);
line().from("device").to("router").arrow("end");
line().from("router").to("cloud").arrow("end");
+line().from("cloud").to("cache").arrow("end");
两行。 row 会为新框腾出位置并推开其它框;而因为连线引用的是名字而不是坐标,原有箭头仍然贴在框上。这一处正是画图工具里最难受的操作:插入点之后的所有东西都得手动拖。
把一个要点降级为口播#
-bullets("41 ms for routed inputs", "72% stay on the device", "Accuracy traded per input");
+bullets("41 ms for routed inputs", "72% stay on the device");
+note("Accuracy is traded per input, not per model.");
移动一行。 一个挤占页面的细节变成了你嘴上说的话。note() 是追加的,所以它可以就近写在它所解释的内容旁边。
用拖的,而不是敲的#
-rect("Cache").as("cache").position({ x: 320, y: 90 });
+rect("Cache").as("cache").position({ x: 404, y: 132 });
两个数字。 实时预览里的 E 控件开启布局编辑,用坐标放置的对象就可以直接拖到位,松手即把数字写回。处在文档流里的对象没有坐标可写,拖动它改变的是它在邻居之间的位置,搬走的是它占据的那几行。
关键在于改完之后长什么样。拖拽产出的,就是你本来会敲出来的那次修改——同样的位置、同样的规模,所以它照样能当源码来评审和 diff。凡是没有唯一一个数字或一行能代表这次拖拽的地方(算出来的坐标、循环里的命令),就没有可拖的手柄,因为写什么都是不诚实的。完整的手势见Visual Studio Code 扩展。
检查结果#
npx frameseq check my-talk.slides.ts
检查器按真实尺寸渲染每一页,报告溢出或被裁切的文字、小于可读下限的字号、空白页,以及拼错的区域路径。改完一轮跑一次:它比逐页点过去快,而且和守着配方页那套检查是同一个。
为什么这些改动都很小#
三条性质做了大部分的功,也值得你在自己的源码里保持:
- 归属就是源码顺序。 一页拥有写在它后面的东西,一个区域拥有写在
at()后面的东西。移动内容就是移动行,永远不需要重新平衡一棵树。 - 对象靠名字关联,不靠数字。
line().from("cloud")能活过任何坐标活不过的布局变化。 - 角色承载样式。
lead()、caption()、metric()表达的是意图,所以换主题是改一行,而不是全局搜索替换。