d2-diagram
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesed2-diagram
d2-diagram
Paths below: is this skill's directory and the directory the skills are
installed in ( in mizchi/explainer, in a project that installed them).
<skill><skills>skills/.claude/skills/A D2 file is the diagram; the picture is a render of it. The writer's job is the
text — ids, labels, containers, connections — and TALA's job is where the boxes
go and how the lines bend. The terminal render exists so the writer reads the
picture on every round without leaving the shell, and so the diagram can live in
a README code block, a PR comment, or a text.
--helpd2
direction: right
web: Web frontend
api: API server
db: PostgreSQL {shape: cylinder}
web -> api: HTTPS
api -> db: SQL .-‾‾‾‾‾‾-.
│╲-______-╱│
HTTPS SQL │ │
┌─────────────┐──────────────▶┌───────────┐─────────────▶│ │
│Web frontend │ │API server │ │PostgreSQL│
│ │ │ │ │ │
└─────────────┘ └───────────┘ ╲-______-╱(, verbatim, D2 0.8.1-HEAD.)
d2 --layout=tala x.d2 x.txtThis skill is for a diagram whose deliverable is D2 text: someone will edit
it later, TALA lays it out, and it renders wherever D2 does. A step-by-step
walk (an algorithm, a request) is not animated: draw the structure once and
number the steps in the prose. To ground the D2 in the code, list the modules
and dependencies from the code first (imports, ), write them into
the fact sheet, and draw exactly those.
package.json下文路径: 表示本 skill 的目录, 表示 skill 的安装目录(在 mizchi/explainer 中为 ,在安装了这些 skill 的项目中为 )。
<skill><skills>skills/.claude/skills/D2 文件才是图本身;图片是它的 render。编写者负责文本——id、label、container、connection——TALA 则负责 box 的位置和连线的弯折方式。terminal render 让编写者每轮都无需离开 shell 就能阅读图表,也让图表可以出现在 README code block、PR comment 或 文本中。
--helpd2
direction: right
web: Web frontend
api: API server
db: PostgreSQL {shape: cylinder}
web -> api: HTTPS
api -> db: SQL .-‾‾‾‾‾‾-.
│╲-______-╱│
HTTPS SQL │ │
┌─────────────┐──────────────▶┌───────────┐─────────────▶│ │
│Web frontend │ │API server │ │PostgreSQL│
│ │ │ │ │ │
└─────────────┘ └───────────┘ ╲-______-╱(,逐字输出,D2 0.8.1-HEAD。)
d2 --layout=tala x.d2 x.txt本 skill 适用于最终交付物是 D2 文本的图表:后续会有人编辑它,由 TALA 负责布局,并且可以在 D2 能 render 的任何位置使用。逐步流程(算法、请求)不需要动画:一次画出结构,并在正文中为步骤编号。为了让 D2 忠实反映代码,先从代码中列出 module 和 dependency(imports、),将它们写入 fact sheet,然后严格按这些内容绘图。
package.jsonInvocation
调用方式
bash
d2 layout # must list `tala (bundled)`. That, not the version
# string, is the gate: v0.8.1-HEAD already bundles it.
d2 --layout=tala arch.d2 arch.txt && cat arch.txt # the terminal render (Unicode box drawing)
d2 --layout=tala --ascii-mode standard arch.d2 arch.txt # plain ASCII (+ - | / < > v) for a README
d2 --layout=tala arch.d2 --stdout-format ascii - # to stdout, for a pipe or a PR comment
d2 --layout=tala arch.d2 arch.svg # the figure for docs
d2 --layout=tala arch.d2 arch.png # a raster to LOOK at (see the loop, step 5)
d2 validate arch.d2 # syntax only; exit 1 with file:line:col
d2 fmt --check arch.d2 # formatted? exit 1 and names the file, changes nothing
d2 fmt arch.d2 # ...and this rewrites it in place
node <skill>/assets/d2-facts.mjs arch.d2 # what the picture DRAWS
node <skill>/assets/d2-facts.mjs arch.d2 --expect f.json # ...held to a sheet
LC_ALL=C.UTF-8 wc -L arch.txt # the width in columns — the locale matters, see belowInstall when is missing — never drive the diagram through an online
playground:
d2bash
curl -fsSL https://d2lang.com/install.sh | sh -s -- # the installer; it rejects the obsolete --tala flag
brew install d2 # macOS
go install github.com/d2lang/d2@latest # Go 1.27+; what to do when the install script gets a 403 through a proxyD2_LAYOUT=talavars: { d2-config: { layout-engine: tala } }.svg.png.txtsequence_diagrambash
d2 layout # must list `tala (bundled)`. That, not the version
# string, is the gate: v0.8.1-HEAD already bundles it.
d2 --layout=tala arch.d2 arch.txt && cat arch.txt # the terminal render (Unicode box drawing)
d2 --layout=tala --ascii-mode standard arch.d2 arch.txt # plain ASCII (+ - | / < > v) for a README
d2 --layout=tala arch.d2 --stdout-format ascii - # to stdout, for a pipe or a PR comment
d2 --layout=tala arch.d2 arch.svg # the figure for docs
d2 --layout=tala arch.d2 arch.png # a raster to LOOK at (see the loop, step 5)
d2 validate arch.d2 # syntax only; exit 1 with file:line:col
d2 fmt --check arch.d2 # formatted? exit 1 and names the file, changes nothing
d2 fmt arch.d2 # ...and this rewrites it in place
node <skill>/assets/d2-facts.mjs arch.d2 # what the picture DRAWS
node <skill>/assets/d2-facts.mjs arch.d2 --expect f.json # ...held to a sheet
LC_ALL=C.UTF-8 wc -L arch.txt # the width in columns — the locale matters, see below缺少 时应进行安装——绝不要通过在线 playground 驱动图表:
d2bash
curl -fsSL https://d2lang.com/install.sh | sh -s -- # the installer; it rejects the obsolete --tala flag
brew install d2 # macOS
go install github.com/d2lang/d2@latest # Go 1.27+; what to do when the install script gets a 403 through a proxy环境变量中的 与该 flag 等效。文件内的 在所测版本中会对 / 生效,但对 不生效(0.8.1-HEAD:text render 回退到 dagre,宽度为 163 列,而不是 120 列),因此仍应传入 flag;同时保留文件中的配置,以便下一位使用者生成 SVG 时使用同一引擎。 是证明该规律的例外——它会自行布局,因此无论是否传入该 flag,其 text render 都逐字节一致。
D2_LAYOUT=talavars: { d2-config: { layout-engine: tala } }.svg.png.txtsequence_diagramNothing in D2 tells you the picture is wrong
D2 本身无法告诉你图表哪里错了
validate--expectd2
edge: { cdn; gateway }
cluster: { orders; inventory }
gateway -> orders # ← two NEW empty boxes at the root; the real ones keep no arrowA writer in this skill's own validation round shipped exactly that:
0, clean, 94 columns, and its log said "can trace all connections". The
render had four duplicated boxes, two orphans, and the system's entry call
drawn as a floating pair outside every region
(report).
d2 validated2 fmtWrite cross-container connections at the root, with full paths. That is the
one form that always works and the one every writer in the round that got this
right used:
d2
edge.gateway -> cluster.orders # at the root: full path on both ends
cluster: {
billing -> _.outside.stripe # from INSIDE: `_` is this container's parent
}_cluster__.outside.stripe_.stripestripe_d2invalid underscoreassets/d2-facts.mjsbash
node assets/d2-facts.mjs arch.d2 # boxes, containers and their members, labels, edges, columns
node assets/d2-facts.mjs arch.d2 --expect arch.facts.json
node assets/d2-facts.mjs arch.d2 --seeds 4,5,6 # the seed the final render will use, so the width it reports is real
node assets/d2-facts.mjs --from-svg arch.svg --from-txt arch.txt # read renders that already exist, no d2 runThe last form is how the checker is itself gated: drives
it over renders committed under with no installed,
so the phantom-box, truncation, reversed-edge and width lines each have a test
that fails if they stop firing. It is also the way to check a render somebody
else produced, or a in a PR you are reviewing.
tests/d2/d2-facts.test.mjstests/d2/fixtures/d2-scenario/d2.svgIt fails on its own, with no sheet, for three defects that are always defects:
a name drawn twice (the scoping trap above), a box that overlaps a sibling or
escapes its container, and a truncated terminal render — a /
pin can push shapes off the ascii canvas, and exits 0 on a text file
holding a fragment. One writer's five-table schema passed at "71 columns" with
three of the five tables missing from the render it had just measured, which is
why the box-by-box comparison exists. It warns about a box nothing connects to.
With a sheet it also checks:
topleftd2json
{
"boxes": ["gateway", "orders", "broker"],
"deps": ["gateway->orders", "orders->broker"],
"forbidden": ["orders->billing"],
"containers": { "cluster": ["orders", "inventory"] },
"order": ["browser->gateway", "gateway->orders"],
"exhaustive": true,
"maxColumns": 100,
"allowOrphans": ["legend"]
}- are checked with direction: a reversed arrow reads
deps.✗ edge reversed: the sheet says broker->billing, the picture draws billing->broker - makes an edge the sheet does not list an error, not a warning.
exhaustive - reads a sequence diagram's messages back by their y position — the one place D2 makes order visible.
order - Names match an id's last segment or its label, so a sheet says for a file that wrote
gateway.gw: API gateway - It cannot see inside a : a column-level edge (
sql_table) is reported asorders.customer_id -> customers.id. Constraint badges are not checked either; count them withorders->customers(grep -o '>FK</text>' arch.svg | wc -lcounts lines, and an SVG is one line, so it prints 1 however many badges there are) and read the PNG for which row each arrow actually leaves.grep -c
Write the sheet from the brief before the diagram, the way a test comes
first. It is the only artifact that survives a TALA reflow.
validate--expectd2
edge: { cdn; gateway }
cluster: { orders; inventory }
gateway -> orders # ← two NEW empty boxes at the root; the real ones keep no arrow本 skill 自身验证轮次中的一位编写者交付的正是这种图: 返回 0, 无问题,宽度为 94 列,日志还声称“can trace all connections”。但 render 中包含四个重复 box、两个孤立节点,系统入口调用则被画成悬浮在所有区域之外的一对节点(报告)。
d2 validated2 fmt在 root 层级使用完整路径编写跨 container 的 connection。 这是始终有效的唯一写法,也是本轮所有编写正确图的人所采用的写法:
d2
edge.gateway -> cluster.orders # at the root: full path on both ends
cluster: {
billing -> _.outside.stripe # from INSIDE: `_` is this container's parent
}_cluster__.outside.stripe_.stripestripe_d2invalid underscoreassets/d2-facts.mjsbash
node assets/d2-facts.mjs arch.d2 # boxes, containers and their members, labels, edges, columns
node assets/d2-facts.mjs arch.d2 --expect arch.facts.json
node assets/d2-facts.mjs arch.d2 --seeds 4,5,6 # the seed the final render will use, so the width it reports is real
node assets/d2-facts.mjs --from-svg arch.svg --from-txt arch.txt # read renders that already exist, no d2 run最后一种形式也用于为 checker 自身建立测试门槛:在未安装 的情况下, 会对提交在 下的 render 执行检查,因此幽灵 box、截断、edge 反向和宽度检查各自都有测试,若不再触发便会失败。也可以用这种方式检查他人生成的 render,或你正在评审的 PR 中的 。
d2tests/d2/d2-facts.test.mjstests/d2/fixtures/d2-scenario/.svg即使没有 fact sheet,它也会针对三种始终属于缺陷的问题失败:同名 shape 被绘制两次(上述作用域陷阱)、box 与 sibling 重叠或逃出 container,以及terminal render 被截断—— / pin 可能把 shape 推出 ASCII canvas,而对于只包含残片的 text 文件, 仍会以 0 退出。一位编写者的五表 schema 在“71 列”处通过检查,但其刚测量的 render 中五张表里有三张缺失,因此才有逐 box 对比。它会对没有任何连接的 box 发出警告。
topleftd2提供 fact sheet 后,它还会检查:
json
{
"boxes": ["gateway", "orders", "broker"],
"deps": ["gateway->orders", "orders->broker"],
"forbidden": ["orders->billing"],
"containers": { "cluster": ["orders", "inventory"] },
"order": ["browser->gateway", "gateway->orders"],
"exhaustive": true,
"maxColumns": 100,
"allowOrphans": ["legend"]
}- 会按方向检查:反向 arrow 会显示为
deps。✗ edge reversed: the sheet says broker->billing, the picture draws billing->broker - 会将 fact sheet 未列出的 edge 视为错误,而不是警告。
exhaustive - 根据 sequence diagram message 的 y 坐标读取顺序——这是 D2 唯一能体现顺序的地方。
order - 名称可以匹配 id 的最后一段或其 label,因此文件写成 时,fact sheet 中仍可写
gw: API gateway。gateway - 它无法看穿 内部:column 级 edge(
sql_table)会被报告为orders.customer_id -> customers.id。Constraint badge 也不会被检查;可用orders->customers统计(grep -o '>FK</text>' arch.svg | wc -l统计的是行数,而 SVG 只有一行,因此无论 badge 数量是多少都输出 1),并查看 PNG 以确认每个 arrow 实际从哪一行离开。grep -c
在绘图之前,先根据需求编写 fact sheet,就像先写测试一样。它是 TALA 重新布局后仍能保留的唯一产物。
How a sheet name finds its shape
fact sheet 中的名称如何匹配 shape
A name in a sheet is not matched on the id alone; the search widens until
exactly one shape matches:
- the last segment of an id — finds
orderscluster.orders - an exact label — finds
gatewaygw: API gateway - a substring of an id, or of a label, when exactly one shape matches
Step 3 is generous on purpose and it will surprise you. A box called
labelled is a sheet's
, so fires on an edge into it. That is
not a bug in either the sheet or the figure — they disagree about a name — so
every line that turns on a loose match now says which match it used:
pgPostgres orders (authoritative until cutover)ordersforbidden: ["checkout->orders"]✗ forbidden edge drawn: checkout->orders (orders matched no id — it is a
substring of box pg's label "Postgres orders (authoritative until cutover)")Read that clause before editing anything: with it, the choice is between
scoping the sheet and fixing the figure. Without it, a writer relabelled a
correct box to satisfy the checker
().
tests/d2/fixtures/d2-scenario/v2/label-substring/fact sheet 中的名称并非只按 id 匹配;搜索范围会逐步扩大,直到恰好匹配一个 shape:
- id 的最后一段——可找到
orderscluster.orders - 完全匹配的 label——可找到
gatewaygw: API gateway - id 或 label 的子字符串,前提是恰好只有一个 shape 匹配
第 3 步有意采取宽松匹配,因此可能出乎意料。名为 、label 为 的 box 就是 fact sheet 中的 ,所以 会对指向它的 edge 触发。这不是 fact sheet 或图本身的 bug——二者对某个名称的理解不一致——因此现在每条依赖宽松匹配的检查结果都会说明具体采用了哪种匹配:
pgPostgres orders (authoritative until cutover)ordersforbidden: ["checkout->orders"]✗ forbidden edge drawn: checkout->orders (orders matched no id — it is a
substring of box pg's label "Postgres orders (authoritative until cutover)")编辑任何内容前先阅读这行说明:有了它,你可以在“收窄 fact sheet 的匹配范围”和“修正图表”之间作出选择;没有它,一位编写者为了满足 checker 而给一个正确的 box 改了 label()。
tests/d2/fixtures/d2-scenario/v2/label-substring/Why TALA, and when not
为什么使用 TALA,以及何时不用
TALA (Terrastruct's AutoLayout Algorithm) is D2's own engine for software
architecture diagrams, open-sourced under MPL-2.0 and bundled with D2 (0.9.0 is
the release the announcement names; the 0.8.1-HEAD build measured here has it
too — ask , not the version). Where dagre and ELK lay a directed graph
out in layers, TALA arranges boxes the way a whiteboard drawing does —
orthogonal, symmetric where it can be, clustered by container — and it is the
only engine that honours every D2 feature: per container, /
on containers, , and / to pin a shape
while the engine places the rest.
d2 layoutdirectionwidthheightnear: <other shape>topleft| The diagram is… | Engine |
|---|---|
| Boxes in containers with connections in several directions (an architecture, a deployment, a data model) | tala |
| One flow with a clear direction, many hops (a pipeline, a DAG) | tala with |
| Thirty-plus shapes | Split by container into two files. TALA's runtime is nonlinear in size: 30 nodes took 0.94s here where ELK took 0.05s; layouts that size also read badly whatever the engine |
Randomness, made reproducible. TALA tries three seeds (,
default ) and keeps the best complete result, so the same file renders the
same picture twice — measured identical. A label change can still cascade into a
different arrangement, so never describe the geometry in prose ("the box on the
left"); name ids. Two measured caveats before you reach for seeds:
--tala-seeds1,2,3- A different seed set often changes nothing. One writer swept twelve seed
sets over a five-table schema and got the identical 109-column layout every
time; another moved 113 → 100 columns with . Try it once, keep the number you measured, and do not plan on it.
--tala-seeds 4,5,6 - Seeds cannot live in the file. says "Diagram data under tala-seeds takes precedence over the command-line flag", but
d2 layout talais rejected at compile time. A committed diagram whose layout depends on a seed only renders that way if every render passes the flag — so write the flag into the script or the Makefile target, or do not rely on it.vars.d2-config.tala-seeds
TALA(Terrastruct's AutoLayout Algorithm)是 D2 自主开发的软件架构图布局引擎,以 MPL-2.0 开源,并随 D2 一起打包(公告中提到的是 0.9.0;这里实测的 0.8.1-HEAD 也包含它——应通过 确认,而不是看版本号)。dagre 和 ELK 会将 directed graph 分层布局,而 TALA 像白板绘图一样安排 box:正交对齐、在可行时保持对称,并按 container 聚类;它也是唯一支持 D2 全部以下特性的引擎:每个 container 的 、container 上的 / 、,以及在引擎安排其余 shape 时用 / 固定某个 shape。
d2 layoutdirectionwidthheightnear: <other shape>topleft| 图表属于…… | 引擎 |
|---|---|
| 多个方向均有连接的 container 中的 box(架构、部署或数据模型) | tala |
| 方向明确、包含多个跳转的单一流(pipeline 或 DAG) | tala,并设置 |
| 超过 30 个 shape | 按 container 拆成两个文件。TALA 的运行时间随规模呈非线性增长:这里 30 个 node 用了 0.94 秒,而 ELK 只用了 0.05 秒;无论使用哪种引擎,这种规模的布局都不易阅读 |
让随机性可复现。 TALA 会尝试三个 seed(,默认值为 )并保留最佳的完整结果,因此同一文件两次 render 会得到相同的图——实测逐字节一致。不过,改动 label 仍可能连锁导致布局变化,因此绝不要在正文中描述几何位置(“左侧的 box”),而应引用 id。在使用 seed 前,有两个经实测得到的注意事项:
--tala-seeds1,2,3- 更换一组 seed 往往不会产生任何变化。 一位编写者针对五表 schema 测试了 12 组 seed,每次都得到完全相同的 109 列布局;另一位则通过 将宽度从 113 降到 100 列。可以试一次,但应以实测数字为准,不要依赖它必然生效。
--tala-seeds 4,5,6 - Seed 不能写在文件里。 的提示为“Diagram data under tala-seeds takes precedence over the command-line flag”,但
d2 layout tala会在编译时被拒绝。若一个已提交的 diagram 布局依赖某个 seed,那么只有每次 render 都传入该 flag 才会得到相同布局——因此请把 flag 写进 script 或 Makefile target,否则不要依赖 seed。vars.d2-config.tala-seeds
Route by task
按任务类型选择写法
| Task shape | Write |
|---|---|
| "Draw the architecture / how the services fit" | Containers per area ( |
| "Draw the data model / tables" | |
| "Draw the request / call flow between components" | |
| "Draw the classes" | |
| "Draw the deployment / network" | Containers for hosts and zones; |
"We already have a | Edit it; |
| "Put it in the README / a code block / a PR comment" | The |
| "It has to be checked against the code" | List the modules and deps from the code (imports, |
| 任务类型 | 写法 |
|---|---|
| “绘制架构/服务如何协作” | 按领域使用 container( |
| “绘制数据模型/表结构” | 使用 |
| “绘制组件之间的请求/调用流程” | 在 root 使用 |
| “绘制类” | 使用 |
| “绘制部署/网络” | 为 host 和 zone 使用 container;存储使用 |
“已经有 | 编辑它;运行 |
| “放入 README/code block/PR comment” | 在 fenced block 中使用 |
| “需要对照代码检查” | 先将代码中的 module 和 dependency(imports、 |
The loop
工作循环
1. write arch.facts.json the boxes, edges and width the brief asks for
2. write arch.d2 ids ASCII; the label after the colon is what the reader sees
3. d2 fmt --check arch.d2 && d2 validate arch.d2 syntax; exit 1 names file:line:col
4. node assets/d2-facts.mjs arch.d2 --expect arch.facts.json
duplicates, missing / reversed / invented edges,
container members, overlaps, order, width
5. d2 --layout=tala arch.d2 arch.png then READ arch.png with your file-reading tool — you can
look at an image, and this is the only step that shows
labels, shapes, key badges and which row an arrow leaves.
Both writers who skipped it reported the same thing:
"a PNG read would have answered this"
6. d2 --layout=tala arch.d2 arch.txt && cat arch.txt the deliverable, if it is going in a terminal
7. fix the text; go to 3Five rounds at most. Each step answers a different question: 3 is "does it
compile", 4 is "does it say what I meant", 5 is "does it read", 6 is "does it fit
where it is going". A compile error is one line —
, — fix the line it names.
arch.d2:2:9: invalid style keyword: "colour"2:1: connection missing destination1. write arch.facts.json the boxes, edges and width the brief asks for
2. write arch.d2 ids ASCII; the label after the colon is what the reader sees
3. d2 fmt --check arch.d2 && d2 validate arch.d2 syntax; exit 1 names file:line:col
4. node assets/d2-facts.mjs arch.d2 --expect arch.facts.json
duplicates, missing / reversed / invented edges,
container members, overlaps, order, width
5. d2 --layout=tala arch.d2 arch.png then READ arch.png with your file-reading tool — you can
look at an image, and this is the only step that shows
labels, shapes, key badges and which row an arrow leaves.
Both writers who skipped it reported the same thing:
"a PNG read would have answered this"
6. d2 --layout=tala arch.d2 arch.txt && cat arch.txt the deliverable, if it is going in a terminal
7. fix the text; go to 3最多进行五轮。每一步回答不同的问题:第 3 步是“能否编译”,第 4 步是“是否表达了我的本意”,第 5 步是“是否易读”,第 6 步是“是否适合目标位置”。编译错误通常只有一行——、——修正它指出的那一行即可。
arch.d2:2:9: invalid style keyword: "colour"2:1: connection missing destinationDone condition
完成条件
- exits 0 and
d2 validateexits 0.d2 fmt --check - listed
d2 layoutand the render used it.tala (bundled) - exits 0: no duplicate name, no truncated render, every listed box and edge drawn with its direction, no forbidden edge, the width inside budget — measured with the same
d2-facts --expectthe final render will use.--seeds - You read the PNG once, and it is one of the deliverables.
- Every shape and connection in the prose is named by its id, and every id in the prose is in the file.
- The is committed where the SVG is; the SVG is a build output or is committed next to it — never only pasted.
.d2
- 和
d2 validate均以 0 退出。d2 fmt --check - 的输出中列出了
d2 layout,且 render 确实使用了它。tala (bundled) - 以 0 退出:不存在重复名称或截断的 render;fact sheet 列出的每个 box 和 edge 都按正确方向绘制;不存在 forbidden edge;宽度未超出预算——测量时使用与最终 render 相同的
d2-facts --expect。--seeds - 你查看过 PNG 一次,且 PNG 是交付物之一。
- 正文中提到的每个 shape 和 connection 都以其 id 标识,且正文中的每个 id 都存在于文件中。
- 与 SVG 提交在同一位置;SVG 要么是 build output,要么与其一同提交——绝不能只粘贴 SVG。
.d2
Width: measure it the way a terminal sees it
宽度:按照 terminal 的显示方式测量
bash
LC_ALL=C.UTF-8 wc -L arch.txt # 122 — correct, and CJK-aware
wc -L arch.txt # 120 in the C locale: an undercount, and 87 vs 111 on a Japanese figure
awk '{if(length($0)>m)m=length($0)}END{print m}' arch.txt # 338 — BYTES. Never this.d2-facts.mjscolumnsThe levers are not monotone. Measure after every one. Measured on two
different diagrams in the same round:
| Lever | Diagram A | Diagram B |
|---|---|---|
root | 144 → 145 (worse) | 148 → 109 (the win) |
| shorter labels | 144 → 146 (worse) | 109 → 135 (worse) |
per-container | 146 → 113 (the win) | — |
| 113 → 100 | no change across 12 seed sets |
| 113 → 74 | 102 → 75 |
So: set the root , then the own of the container holding
the most boxes — that is the first thing to try, not the last — and re-measure
after each. Shortening labels is as likely to make it wider, because TALA reflows
the whole picture. does not change the text render at all. None of this
applies to a , which lays itself out: there the only lever is
label length, and it moves the width by a column or two in either direction.
directiondirection--scalesequence_diagramTwo caveats on the escape hatches:
- Per-container is often ignored. Setting it on two containers produced byte-identical renders; on the fullest one it moved 33 columns. TALA appears to honour it only where the container's internal flow is ambiguous.
direction - /
topare SVG pixels, not columns, and both must be set together (TALA only). Roughly 11.5px per terminal column on a 100-column figure, but every writer who used it found its numbers by trying three to five values. Pin the one or two shapes whose place matters, comment why, and expect the pin to need re-tuning after the next label change.left - A pin can truncate the terminal render, and that looks like a win. One
writer pinned a table, watched the width fall 113 → 71, and the number was a
fragment: the pin had pushed shapes off the ascii canvas and three of five
tables were gone from the text while the SVG had grown. exits 0 on it.
d2now reports it as an error; if you pin, re-read the render.d2-facts - The budget may be unreachable without a pin. On five s no documented lever got under 100 columns:
sql_tablereached 113, shorter labels 127, a wrapping container 121, and only the unsafe pin went lower. When that happens, split the diagram by container into two files, or keep the wider figure and say in the prose that the terminal version is 113 columns — do not buy the number with a pin you have not re-read.direction: down
bash
LC_ALL=C.UTF-8 wc -L arch.txt # 122 — correct, and CJK-aware
wc -L arch.txt # 120 in the C locale: an undercount, and 87 vs 111 on a Japanese figure
awk '{if(length($0)>m)m=length($0)}END{print m}' arch.txt # 338 — BYTES. Never this.d2-facts.mjscolumns这些调节手段并非单调。每调整一次都要重新测量。 在同一轮中对两个不同 diagram 的实测结果如下:
| 调节手段 | Diagram A | Diagram B |
|---|---|---|
root 的 | 144 → 145(更差) | 148 → 109(成功) |
| 缩短 label | 144 → 146(更差) | 109 → 135(更差) |
在最复杂的 container 上设置自身 | 146 → 113(成功) | — |
| 113 → 100 | 12 组 seed 均无变化 |
对一个 shape 使用 | 113 → 74 | 102 → 75 |
因此:先设置 root 的 ,然后设置包含 box 最多的 container 自身的 ——这应该是首先尝试的手段,而不是最后才尝试——并在每次调整后重新测量。缩短 label 反而同样可能让图变宽,因为 TALA 会重新排布整张图。 完全不会改变 text render。上述方法均不适用于 ,因为它会自行布局;对它而言,唯一的调节手段是 label 长度,而长度只会让宽度增加或减少一两列。
directiondirection--scalesequence_diagram使用这些非常规手段时要注意两点:
- 每个 container 的 经常会被忽略。 在两个 container 上设置它,得到的 render 逐字节相同;设置在 box 最多的 container 上,则使宽度变化了 33 列。TALA 似乎只会在 container 内部流向不明确时才采用该设置。
direction - /
top的单位是 SVG 像素,而不是列,而且必须同时设置两者(仅限 TALA)。在 100 列的图中,每 terminal 列大约对应 11.5px,但每位使用它的编写者都是通过尝试三到五个值来找到合适数值。固定位置重要的一两个 shape,添加注释说明原因,并预期下次修改 label 后需要重新调整 pin。left - Pin 可能截断 terminal render,而且表面上看起来像是优化成功。 一位编写者固定了一张 table,看到宽度从 113 降到 71,但这个数字只对应一段残片:pin 将 shape 推出了 ASCII canvas,五张 table 中有三张从 text 中消失,而 SVG 反而变大了。仍会返回 0。现在
d2会将其报告为错误;使用 pin 后必须重新检查 render。d2-facts - 不使用 pin 时,宽度预算可能无法达到。 对五个 ,所有有文档的调节手段都无法压到 100 列以内:
sql_table降到 113,缩短 label 后为 127,使用 wrapping container 后为 121,只有不安全的 pin 能进一步降低。此时应按 container 将 diagram 拆成两个文件,或者保留较宽的图,并在正文中说明 terminal 版本为 113 列——不要用尚未重新检查过的 pin 来换取数字。direction: down
Reading the terminal render
阅读 terminal render
Measured on 0.8.1-HEAD. Read structure here; read labels and shapes on
the PNG.
- Edge labels are written onto the line and their spaces become line
characters (,
vlmkit check integrity─page─html). Keep terminal-bound edge labels to one or two words.201─Created - Every connection style is dropped. ,
stroke-dash,strokerender as a plain line: a writer diffed its styled render against a style-free copy and they were identical. Dashed-means-async does not survive, so carry the distinction in the label.bold - Arrowheads are unreliable on long labels. A label long enough to fill the
lane can leave the arrow with no head at all, and in a label is eaten outright. Which character the line eats is not stable either: the same file lost a label's parentheses on one round and its full stop on the next (
<<→order.placed). Probe the spelling you land on, and prefer one short word with no punctuation.order─placed - and
shape: textblocks render as empty boxes. A title, a legend or a paragraph belongs in the prose around the render, or in a variant of the file rendered only to SVG.|md …| - rows render, their constraint badges do not. PK / FK markers are visible only in the SVG / PNG, and a column-level edge attaches to the wrong row in the text view often enough that you cannot read it there.
sql_table - An edge label can land on a box or container border (), which reads as two boxes merged. Where it lands is positional, not length-driven: a writer whose connection was already at the root both shortened and lengthened the label and it stayed on the same border row at the same width. The levers that do move it are the ones that reflow the picture — the container's
┌────sync call────┐│, a different seed — so treat it as a layout outcome to check on the PNG, not a label to trim.direction - CJK labels pad every glyph () and misalign the box that holds them. Ids stay ASCII always; for a figure that will be read in a terminal keep the labels ASCII too and put the Japanese in the surrounding text. The SVG and PNG render Japanese correctly.
ウ ェ ブ - mode keeps the same layout with
standardonly — the version for a README read on GitHub, where box-drawing glyphs in a code block depend on the viewer's font.+ - | / < > v - Icons and images are dropped; sequence diagrams, and
sql_tableshapes render, with their rows.class
以下结果基于 0.8.1-HEAD 实测。这里用于阅读结构;label 和 shape 应在 PNG 上阅读。
- Edge label 会直接写在连线上,其中的空格会变成线条字符(、
vlmkit check integrity─page─html)。面向 terminal 的 edge label 应控制在一到两个词。201─Created - 所有 connection style 都会被丢弃。 、
stroke-dash、stroke均会 render 为普通直线:一位编写者对带 style 的 render 与无 style 副本做 diff,结果完全相同。虚线表示 async 这一信息无法保留,因此应将区别写入 label。bold - Label 较长时,arrowhead 不可靠。 足以填满 lane 的 label 可能让 arrow 完全没有头部,而 label 中的 也会被直接吞掉。线条吞掉哪个字符并不稳定:同一文件有一轮丢失 label 的括号,下一轮又丢失句号(
<<→order.placed)。应检查最终呈现的拼写,并优先使用一个不含标点的短词。order─placed - 和
shape: textblock 会 render 为空 box。 标题、legend 或段落应放在 render 周围的正文中,或者放进一个仅 render 为 SVG 的文件变体。|md …| - 的 row 会 render,但 constraint badge 不会。 PK / FK 标记仅在 SVG / PNG 中可见;而且 column 级 edge 在 text view 中经常连接到错误的 row,无法据此读取实际关系。
sql_table - Edge label 可能落在 box 或 container 边框上(),看起来就像两个 box 合并了。其落点由位置决定,而不是由长度决定:一位编写者的 connection 已写在 root,他既缩短又加长了 label,但它仍停留在同一宽度下的同一行边框上。真正能移动它的手段是那些会重新排布整张图的方式——container 的
┌────sync call────┐│、不同的 seed——因此应把它视为需要在 PNG 上检查的布局结果,而不是靠裁剪 label 来解决。direction - CJK label 会为每个 glyph 补齐宽度(),导致容纳它们的 box 错位。Id 必须始终使用 ASCII;对于将在 terminal 中阅读的图,label 也应保持 ASCII,把日文放在周围的正文中。SVG 和 PNG 能正确 render 日文。
ウ ェ ブ - mode 保持相同布局,但只使用
standard——这是面向在 GitHub 上阅读 README 的版本,因为 code block 中的线框字符效果取决于阅读者所用字体。+ - | / < > v - Icon 和 image 会被丢弃;sequence diagram、与
sql_tableshape 则会连同其 row 一起 render。class
Rules the writer keeps
编写者应遵守的规则
- Ids are ASCII and stable (, not
api); the label goes after the colon. Abbreviating an id is fine —API servermatches on the label too — but the next editor reads the id first.d2-facts - A cross-container connection is written at the root or with , and
_.is one level: from inside_, the root iscluster, so a shape in a sibling container is_. Never let an out-of-scope name stand: it becomes a new box (see above)._.outside.stripe - One root ; a container that must flow the other way sets its own (TALA only, and only sometimes honoured).
direction - Connections read as sentences: .
a -> b: verbis undirected,--both ways. A style on a connection (<->) is invisible in the terminal — if the distinction matters there, put it in the label.{style.stroke-dash: 3} - at the top of every file, so a render without the flag still gets TALA for the SVG.
vars: { d2-config: { layout-engine: tala } } - No coordinates unless a shape must be pinned; then and
toptogether, on that shape only, and a comment saying why.left
- Id 应使用 ASCII 且保持稳定(用 ,而不是
api);label 写在冒号后。可以缩写 id——API server也会匹配 label——但下一位编辑者会先阅读 id。d2-facts - 跨 container 的 connection 应写在 root,或使用 ,且
_.只表示一级:在_内部,root 是cluster,因此 sibling container 中的 shape 写作_。绝不能保留超出作用域的名称:它会变成新的 box(见上文)。_.outside.stripe - Root 只设置一个 ;需要采用相反流向的 container 设置自身
direction(仅 TALA 支持,而且并非总会采用)。direction - Connection 应像句子一样阅读:。
a -> b: verb表示无方向,--表示双向。Connection 上的 style(<->)在 terminal 中不可见——如果该区别在那里很重要,请写入 label。{style.stroke-dash: 3} - 每个文件顶部都写上 ,这样即使 render 时未传 flag,SVG 仍会使用 TALA。
vars: { d2-config: { layout-engine: tala } } - 除非必须固定某个 shape 的位置,否则不要使用坐标;需要固定时,只在该 shape 上同时设置 和
top,并添加注释说明原因。left
Deliver
交付
- In a chat: the terminal render in a fenced block (it is the picture in a
terminal), the ids named in the prose, the and the PNG / SVG as files (
.d2when available).SendUserFile - In a PR or issue: the render in a fenced block, or the SVG committed and linked. The
standardin the diff is the review target..d2 - In docs: the committed next to the page and the SVG generated by the build (
.d2in a script), or the SVG committed when the site has no build step.d2 --layout=tala docs/arch.d2 docs/arch.svg
- 在聊天中:以 fenced block 展示 terminal render(它就是 terminal 中的图片),在正文中点名 id,并将 与 PNG / SVG 作为文件提供(可用时使用
.d2)。SendUserFile - 在 PR 或 issue 中:在 fenced block 中展示 render,或者提交并链接 SVG。diff 中的
standard才是评审对象。.d2 - 在文档中:把 提交到页面旁边,并由 build 生成 SVG(在 script 中执行
.d2);如果站点没有 build step,则直接提交 SVG。d2 --layout=tala docs/arch.d2 docs/arch.svg
Failure modes
故障模式
- → install (above); do not draw it in another tool.
d2: command not found - does not list
d2 layout→ an old release; upgrade. The installer'stalaflag no longer exists and is rejected.--tala - A box appears twice, or an arrow you wrote is nowhere → an out-of-scope
reference created a phantom. names it; the fix is a full path or
d2-facts, not another arrow._. - The text render came out dagre-shaped and wide though the file says
→ the
layout-engine: talapath ignores the in-file config on some builds; pass.txt.--layout=tala - /
"tala-seeds" needs a value→ seeds are a flag only.is not a valid config - ,
connection missing destination→ the line and column are named; usually a trailingmaps must be terminated with }or an unclosed brace.-> - → D2 spells
invalid style keyword,color,stroke,stroke-dash,fill,font-size,bold,opacity,shadow,3d.multiple - Every round moves the boxes though you changed one label → TALA reflowed. Pin the few shapes whose place matters, or accept it and stop describing positions.
- Two boxes look like they touch and you cannot tell → reports sibling overlaps in px²; D2 itself has no collision report.
d2-facts
assets/vlmkit-workspace.d2assets/vlmkit-workspace.txttests/d2/fixtures/d2-scenario/docs/reports/2026-09-14-d2-diagram-v1.md- → 按上文方式安装;不要改用其他工具绘图。
d2: command not found - 未列出
d2 layout→ 版本过旧;请升级。安装器的talaflag 已不存在,使用时会被拒绝。--tala - 某个 box 出现两次,或你写的 arrow 完全不见 → 超出作用域的引用创建了幽灵节点。会指出它;修复方式是使用完整路径或
d2-facts,而不是再添加一条 arrow。_. - 尽管文件写着 ,text render 却呈现 dagre 风格且很宽 → 某些构建在处理
layout-engine: tala时会忽略文件内配置;请传入.txt。--layout=tala - /
"tala-seeds" needs a value→ seed 只能通过 flag 设置。is not a valid config - 、
connection missing destination→ 错误会指出具体行和列;通常是末尾多了maps must be terminated with }或花括号未闭合。-> - → D2 中的正确拼写是
invalid style keyword、color、stroke、stroke-dash、fill、font-size、bold、opacity、shadow、3d。multiple - 只改了一个 label,却每轮都有 box 移动 → TALA 重新布局了。可固定位置重要的少数 shape,或接受变化并停止描述位置。
- 两个 box 看起来相接却无法判断 → 会以 px² 报告 sibling 重叠;D2 本身没有 collision report。
d2-facts
assets/vlmkit-workspace.d2assets/vlmkit-workspace.txttests/d2/fixtures/d2-scenario/docs/reports/2026-09-14-d2-diagram-v1.md