图示
LifecycleFigure - 生命周期图
一台状态机:一个东西可能处于什么状态,以及什么让它在状态之间移动。
用法
什么时候用它
来自@misoto22/folio/diagrams
import { LifecycleFigure } from '@misoto22/folio/diagrams'说明
一台状态机:一个东西可能处于什么状态,以及什么让它移动。
这是这里唯一会花颜色的图,而且它花的正好是这套系统留给状态的那两个 token:终态成功用 --success,终态失败用 --danger。这个包里没有别的东西被允许用这两个,而生命周期图正是当初为它们留位置的那个场景——这张图的全部意义,就是一次运行是从哪一头出来的。
另外六种靠形状承载,所以一份黑白打印只丢掉两个结局的色相,其余每一处区分都还在:起点是一块填充的胶囊帽,判定是一个菱形,等待是一个虚线框,外部是一块切掉一角的板。
可恢复的失败,是有路可回的失败。type: "failure" 说的是一个状态怎么画;从它出去的那条转移,才说明这次运行是不是结束了。一个可重试的错误和一个终态的错误长得一模一样,直到你顺着箭头走——这是对的:两者之间的差别是机器里一条真实的边,不是盒子上的一个形容词。
结构
| 部件 | 说明 |
|---|---|
| 图外框必填 | 画面外面的那个框。一台状态机交给它的是一份转移列表,每条 note 跟在一个破折号后面并了进去,所以箭头上的条件——超时、重试次数——在文字副本里,而不只在线上。 |
| 状态板块必填 | 一种 kind 一种形状:起点是一枚实心帽,判断是菱形,等待是虚线框,外部是切角,两种终态用的是系统专门留给它们的那两块底色。名字居中,step 在行首那个角上,tag 沿着底边排。 |
| 主轨 | 第一条泳道上的状态,按 col 排序后依次相连,用的是强调的线重——transitions 还没声明的每一对相邻状态各补一条线,所以写全了的脊梁由 transitions 画出来,留着没写的脊梁照样被画出来。没有它,顶行读起来就是五个互不相连的方框,因为一台状态机的脊梁正是没人会写下来的那一部分。 |
| 转移 | 一条布好线的连线,label 落在遮罩上,note 用更轻的一行排在下面。两个主轨邻居之间若声明了转移,它会替换掉那条隐含的线,而不是叠成两条。 |
| 泳道 | 一条线,上方配一个等宽小标题,只画到这条泳道自己的状态所及之处。第一条之后的泳道,它的列会沿着主轨整体错开,正是这一下让从脊梁上掉下来的那一支是竖直的,而不是拐一道折。 |
实践建议
推荐
- 第一条泳道只留给那条有顺序的脊梁,别的什么都不放。那里相邻的状态即便没有任何转移声明这一对,也会被隐含的主轨连起来,所以一个为了排版塞进 lane 0 的状态,会凭空造出一条这台状态机没有的边。而一个 lane id 谁都没有声明过的状态会被挡在这条主轨之外,改为报出 DIAGRAM_LANE_UNKNOWN——一个拼写错误不该有本事加出一根箭头。
- 次级泳道的列要从主轨算起。第一条之后的泳道里,每个 col 都整体后移两列,所以第二条泳道的 col: 0 落在第一条泳道 col: 2 的下方——这是 archify 的生命周期契约本来就隐含的约定,在这里重申一遍,因为正是它让一支下落落在它出发的那个状态上。
- 一次失败能不能恢复,要用边来说,不是用 type 来说。type: "failure" 只是给板块上色;可重试的错误和终结的错误,在读者顺着出边走一遍之前是一模一样的,而没有出边的那个,就是这次运行结束的地方。
避免
- yOffset 收下了,而且是有意不生效。它是按另一个渲染器的泳道高度量出来的一记微调,所以一个全靠它才躲开邻居的状态,在这里会回到自己泳道的中线上——两块因此挨得太近的板块,要的是它们中间多一列,不是推一把。
- 这张图声称的是:一个状态只能按它的箭头所说的那些方式离开。一次哪儿都能发生、却哪儿都没画的取消,读起来就是不可能发生——这是画面做出的判断,规格从来没有写过——那就把它画出来,或者在副标题里说清楚它没有画。
示例
default
顶上那根轨并不在 spec 里。一张生命周期图会把第一条泳道让给那些有序的阶段,而那里相邻两列之间的箭头会按强调分量替你画好,所以作者只需声明那些离开主干的边——这里就是穿过 Blocked 的那三条。这也是唯一一张被允许用颜色的图,而它恰好花掉两个 token:一个给完成状态,一个给失败状态。其余每一类都由形状承载,所以一次灰度打印丢掉的是那两个结局,其他每一处区分都还在。
A job run
6 elements and 6 relationships. The only figure that spends colour, on the two outcomes.
- Lane: Phases
- Queued (start) — accepted — step 01
- Running (active) — tool calls — step 02
- Review (decision) — quality gate — step 03
- Completed (success) — final answer — step 04
- Lane: Interruptions
- Blocked (waiting) — missing input
- Lane: Terminal
- Failed (failure) — timed out
- Queued → Running
- Running → Review
- Review → Completed
- Review → Blocked: needs input
- Blocked → Failed: expired — after 24h
- Blocked → Running: answered
a decision with both ends
一个决策,两个末端都画了出来。Approved 坐在轨上,所以它那条箭头是隐含的;Declined 坐在第二条泳道里,而这是在这里从一个决策扇出的唯一方式——往第一条泳道里再加第四个状态,它会被那条隐含的轨和邻居连上,无论这个转移是否真的存在。那两个终态是这个包里仅有的被允许用颜色的记号,而这张图正是那两个 token 被预留下来所针对的:读者带到一台状态机跟前的问题,是「这一趟是从哪个末端出来的」。
Card authorisation
4 elements and 3 relationships. One decision, and the two ends a run can come out of.
- Lane: Authorisation
- Submitted (start) — card token — step 01
- Risk score (decision) — model and rules — step 02
- Approved (success) — funds held — step 03
- Lane: Refusals
- Declined (failure) — no retry
- Submitted → Risk score
- Risk score → Approved
- Risk score → Declined: score above 90
- A run leaves through Approved or through Declined and nowhere else.
- Submitted to Risk score to Approved is drawn without being declared.
a failure with a way back
两个 failure 类型的状态,画得一模一样,而其中只有一个会终结这一趟。板子上没有任何东西说明一次失败是不是终态——差别是机器里一条真实的边,所以 Timed out 有一条回到 Sending 的箭头,Rejected 没有,而读者是靠追箭头、不是靠读一个形容词来分辨它们的。那条重试边在它的文案下面带着一条注记,而当把某个计数或条件折进标签里会让标签长到塞不进那道空隙时,它就该去那儿。
A delivery attempt
5 elements and 5 relationships. Two failures on the same plate; one of them is not the end.
- Lane: Delivery
- Queued (start) — accepted — step 01
- Sending (active) — attempt n — step 02
- Delivered (success) — 2xx — step 03
- Lane: Faults
- Timed out (failure) — no response
- Rejected (failure) — no retry
- Queued → Sending
- Sending → Delivered
- Sending → Timed out: no reply in 10s
- Timed out → Sending: retry — up to 5 times
- Sending → Rejected: 400 invalid body
- Timed out and Rejected are drawn identically.
- Only the arrow leaving Timed out says the run continues.
the implicit rail
三个关于摆放的事实,每个都只会咬你一次。声明主干上本来就有的一对,不是重复——它会替换掉那条隐含的边,并采用你的标签——所以给某一步命名的办法是把那个转移写出来,而不是指望那根轨替你带上。第二条泳道的第 N 列,坐在主泳道第 N + 2 列的下面,这也是为什么位于第 0 列的 Withdrawn 落在 Accept 底下、而不是 In review 底下。还有,yOffset 在一个状态上是被接受、然后刻意被忽略的:那点微调是按 archify 的泳道深度来量的、不是这里的,所以照做会把一块板子掉到下面那条泳道里。
An article, from draft to live
6 elements and 6 relationships. The spine is drawn; only the departures are written.
- Lane: Review
- Draft (start) — author only — step 01
- In review (active) — two readers — step 02
- Accept? (decision) — editor — step 03
- Published (success) — on the site — step 04
- Lane: Off the rail
- Withdrawn (neutral) — by the author
- Held (waiting) — awaiting legal
- Draft → In review
- In review → Accept?
- Accept? → Published
- In review → Withdrawn: author pulled it
- Accept? → Held: legal question — blocks publication
- Held → Accept?: cleared
无障碍
- success 和 failure 是这个包的图里仅有的两处彩色标记,而且各自还有独立的板块形状——颜色从来不是唯一的载体。
- 就算 spec 没有列出这些转移,主轨也会在第一条泳道的相邻状态之间画出来,因为那是图的脊梁,不是它的例外。