@cardenelabs/cdl 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +343 -0
- package/SPEC.md +374 -0
- package/dist/index.cjs +28085 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3415 -0
- package/dist/index.d.ts +3415 -0
- package/dist/index.js +27962 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +23043 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +2 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +23041 -0
- package/dist/react.js.map +1 -0
- package/dist/render-C78lIXeC.d.cts +2449 -0
- package/dist/render-C78lIXeC.d.ts +2449 -0
- package/examples/quick-start.md +88 -0
- package/package.json +79 -0
- package/src/anim/core/easing.ts +77 -0
- package/src/anim/core/timeline.ts +335 -0
- package/src/anim/core/types.ts +42 -0
- package/src/anim/index.ts +20 -0
- package/src/anim/react/index.ts +3 -0
- package/src/anim/react/useReducedMotion.ts +27 -0
- package/src/anim/react/useTimeline.ts +48 -0
- package/src/assert-never.ts +16 -0
- package/src/author-intent-verify.ts +587 -0
- package/src/builder.ts +3740 -0
- package/src/compile.ts +12 -0
- package/src/dom-verify-core.ts +121 -0
- package/src/dom-verify-types.ts +21 -0
- package/src/dom-verify.ts +948 -0
- package/src/event-handler/index.ts +184 -0
- package/src/formula/ast.ts +46 -0
- package/src/formula/evaluator.ts +163 -0
- package/src/formula/formula-computeds.ts +83 -0
- package/src/formula/index.ts +5 -0
- package/src/formula/parser.ts +399 -0
- package/src/index.ts +284 -0
- package/src/input-signals.ts +74 -0
- package/src/kinds/actor.tsx +79 -0
- package/src/kinds/card.tsx +61 -0
- package/src/kinds/chart-bar.tsx +121 -0
- package/src/kinds/chart-line.tsx +163 -0
- package/src/kinds/chart-pie.tsx +107 -0
- package/src/kinds/compact-title.ts +164 -0
- package/src/kinds/dyn-shape.tsx +394 -0
- package/src/kinds/event.tsx +70 -0
- package/src/kinds/function.tsx +53 -0
- package/src/kinds/funnel.tsx +122 -0
- package/src/kinds/gantt.tsx +193 -0
- package/src/kinds/generic.tsx +368 -0
- package/src/kinds/mind-map.tsx +367 -0
- package/src/kinds/mind-radial.tsx +209 -0
- package/src/kinds/node-tone.ts +18 -0
- package/src/kinds/quadrant.tsx +164 -0
- package/src/kinds/row-align.ts +179 -0
- package/src/kinds/shape-basement.tsx +683 -0
- package/src/kinds/shape-blockchain.tsx +750 -0
- package/src/kinds/shape-commerce.tsx +553 -0
- package/src/kinds/shape-finance.tsx +706 -0
- package/src/kinds/shape-hardware.tsx +862 -0
- package/src/kinds/shape-people.tsx +439 -0
- package/src/kinds/shape-region.tsx +383 -0
- package/src/kinds/shape-software.tsx +859 -0
- package/src/kinds/storage.tsx +180 -0
- package/src/kinds/text-width.ts +73 -0
- package/src/kinds/tree.tsx +275 -0
- package/src/kinds/user-journey.tsx +240 -0
- package/src/label-text.ts +71 -0
- package/src/layout/clearance-constants.ts +47 -0
- package/src/layout/collisions.ts +2078 -0
- package/src/layout/edges.ts +2498 -0
- package/src/layout/footer-shape.ts +97 -0
- package/src/layout/geometry.ts +135 -0
- package/src/layout/label-shift.ts +28 -0
- package/src/layout/lanes.ts +328 -0
- package/src/layout/nodes.ts +190 -0
- package/src/layout/predict-bbox.ts +76 -0
- package/src/layout/px-projection.ts +176 -0
- package/src/layout/spec.ts +872 -0
- package/src/layout/text-width.ts +133 -0
- package/src/layout/tokens.ts +181 -0
- package/src/layout/viewbox.ts +47 -0
- package/src/layout-with-validation.ts +175 -0
- package/src/layout.ts +381 -0
- package/src/presets.ts +2108 -0
- package/src/reactive/batch.ts +48 -0
- package/src/reactive/computed.ts +85 -0
- package/src/reactive/effect.ts +145 -0
- package/src/reactive/index.ts +6 -0
- package/src/reactive/internal.ts +109 -0
- package/src/reactive/signal.ts +113 -0
- package/src/render/edges.tsx +318 -0
- package/src/render/header.tsx +146 -0
- package/src/render/interactive-panel.tsx +9620 -0
- package/src/render/nodes.tsx +319 -0
- package/src/render/stage.tsx +377 -0
- package/src/render/tone.ts +64 -0
- package/src/render/utils.ts +238 -0
- package/src/render.tsx +221 -0
- package/src/scroll-trigger/index.ts +109 -0
- package/src/scroll-trigger/progress.ts +49 -0
- package/src/thumbnail.tsx +114 -0
- package/src/types.ts +2371 -0
- package/src/validate.ts +268 -0
- package/src/visual-validate.ts +4381 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Chainome Diagram Language (cdl) — Spec v1
|
|
2
|
+
|
|
3
|
+
chainome のアニメーション図を **30-50 行の TS で宣言** するための DSL。
|
|
4
|
+
|
|
5
|
+
mermaid の 1 ファイル text + 著者性、 manim の phase 進行、 motion canvas の state tween を
|
|
6
|
+
TS の fluent API で 1 つにまとめた、 chainome 専用の高水準層。
|
|
7
|
+
|
|
8
|
+
## 関連 repo (相互リンク SSOT)
|
|
9
|
+
|
|
10
|
+
- **cdl** (本 repo) ... engine SSOT + layout / routing / rendering (`packages/cdl/src/**`)
|
|
11
|
+
- **[dragon](https://github.com/cardene777/dragon)** ... cdl の text DSL 版 + playground + docs
|
|
12
|
+
- `packages/dragon/` = YAML-like text DSL (`textDslToDiagram` API、 cdl engine を wrap)
|
|
13
|
+
- `apps/playground/` = Astro-based docs site + editor + visual regression tests
|
|
14
|
+
- visual regression / overlap-detector / visual-diagnostics gate は本 SPEC の SSOT を
|
|
15
|
+
`@cardenelabs/cdl` の import で使う (pixel-perfect assertion via viewBox scale)
|
|
16
|
+
- dragon 側 pixel-perfect gate 実装 ... `apps/playground/tests/visual/visual-diagnostics.spec.ts`
|
|
17
|
+
|
|
18
|
+
## 設計の核
|
|
19
|
+
|
|
20
|
+
- 1 トピック = 1 file = `topics/<slug>.cdl.ts`
|
|
21
|
+
- 著者は **概念 (lane / node / edge / state / phase) だけ書く**、 座標 / path / 影 / 矢印形は engine が出す
|
|
22
|
+
- 1 ファイルで「構造 + 状態 + 時系列」 を完結 (mermaid に無いアニメ表現を必須一級概念に)
|
|
23
|
+
- 既存 `@cardenelabs/anim` (Timeline + SVG primitive) をラッパーして describe
|
|
24
|
+
- 完全 ブラウザ標準依存 (SVG + Web Animations API)、 graph theory library に依存しない
|
|
25
|
+
|
|
26
|
+
## Scope boundary (helper 追加基準 SSOT)
|
|
27
|
+
|
|
28
|
+
cdl は **chainome アニメーション図の SVG geometry DSL**。 helper / axis / module を追加する前に本 boundary を満たすか確認する、 満たさなければ追加しない (別 repo / 別 package に切出す)。
|
|
29
|
+
|
|
30
|
+
必須条件 — helper 1 個追加する前に以下 3 点を全て満たす。
|
|
31
|
+
|
|
32
|
+
1. **SVG geometry と因果接続がある** — 追加 helper が最終的に SVG output の座標 / clearance / label 位置 / arrow angle / lane layout / phase transition のいずれかを検証・変換・生成する。 因果連鎖が「helper → visualValidate → SVG geometry」 で説明できる。
|
|
33
|
+
2. **downstream (chainome / dragon playground) から production code で呼ばれる予定がある** — test file だけで呼ばれる helper は追加しない。 「将来使うかも」 は追加根拠にならない、 実 use case が確定してから追加する。
|
|
34
|
+
3. **5 プリミティブ (lane / node / edge / state / phase) の 1 級概念に閉じる** — MLIR IR / SMT / VM / WASM / consensus / P2P / SBOM / codemod / MCP / plugin 等 5 プリミティブ外の抽象は追加しない、 別 repo で扱う。
|
|
35
|
+
|
|
36
|
+
失格例 (2026-07-03 rollback で削除した 9 file の教訓) — `visual-validate-{ai,compiler,devx,distributed,experimental,observability,realtime,supply,toolchain}.ts` は SVG geometry と因果接続なし + production 呼出 0 + 5 プリミティブ外の抽象 (VM / audit chain / WASM host bindings 等) を追加していた。 pure dead code + SSOT 汚染で全削除、 詳細は commit history。
|
|
37
|
+
|
|
38
|
+
## 5 プリミティブ
|
|
39
|
+
|
|
40
|
+
### 1. `lane`
|
|
41
|
+
|
|
42
|
+
縦カラム。 ノードを入れる箱。 必要なら boundary を描いてラベルを付ける。
|
|
43
|
+
**lane が x 軸の column を決める**、 lane 内で node は stack 順に縦並び。
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
diagram.lane(id, {
|
|
47
|
+
x: number, // viewBox 内の x 座標 (左端)
|
|
48
|
+
width: number, // lane 幅
|
|
49
|
+
label?: string, // 上部に表示する lane タイトル
|
|
50
|
+
contain?: boolean, // true なら破線 boundary を描く (lane 全体を囲む)
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
例 ... `actors` (320px) / `contract` (760px) / `output` (280px) の 3 lane。
|
|
55
|
+
|
|
56
|
+
### 2. `node`
|
|
57
|
+
|
|
58
|
+
lane 内に置かれる要素。 kind で見た目を切替。
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
diagram.node(id, {
|
|
62
|
+
lane: string, // 所属 lane id
|
|
63
|
+
stack: number, // lane 内の縦順 (0 = 上、 大きいほど下)
|
|
64
|
+
kind: NodeKind, // "actor" | "function" | "storage" | "event" | "card"
|
|
65
|
+
title: string, // 大文字主タイトル (16-22px 日本語太字)
|
|
66
|
+
eyebrow?: string, // 上の小ラベル (10-11px accent 色 日本語)
|
|
67
|
+
subtitle?: string, // タイトル下の補足 (11-12px 日本語)
|
|
68
|
+
value?: string, // 数値表示用 (mono、 24-30px、 state 参照可 `{state}` 形式)
|
|
69
|
+
rows?: string[], // kind=storage 時の行 (各行 mono、 state 参照可)
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
NodeKind 一覧 (cdl が用意する built-in 描画)。
|
|
74
|
+
|
|
75
|
+
| kind | 用途 | 見た目 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `actor` | 人 / アドレス | title 大 + eyebrow + value (残高) |
|
|
78
|
+
| `function` | コントラクト関数 | title (mono) + subtitle |
|
|
79
|
+
| `storage` | 保存データ | title + eyebrow + rows (各 mono) |
|
|
80
|
+
| `event` | イベントログ | title (mono) + subtitle |
|
|
81
|
+
| `card` | 汎用 | title + subtitle のみ |
|
|
82
|
+
|
|
83
|
+
### 3. `edge`
|
|
84
|
+
|
|
85
|
+
node-node の関係。 layout engine が orthogonal routing で 90° L 字を自動算出。
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
diagram.edge(fromId, toId, {
|
|
89
|
+
id: string, // phase で参照するため必須
|
|
90
|
+
label: string, // 日本語太字 (13px)
|
|
91
|
+
sub?: string, // sublabel mono (12px、 コード片 OK)
|
|
92
|
+
tone: "accent"|"teal"|"success"|"error"|"warning"|"info",
|
|
93
|
+
side?: "top"|"right"|"bottom"|"left", // 出口側 hint、 layout engine への指示
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
side hint がない場合、 layout engine が「lane 関係」 から自動推定。
|
|
98
|
+
|
|
99
|
+
| 起点 lane | 終点 lane | 推定 side |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| 左 lane → 右 lane | 起点 right、 終点 left |
|
|
102
|
+
| 同 lane 内、 上 → 下 | 起点 bottom、 終点 top |
|
|
103
|
+
| 同 lane 内、 同 stack、 異なる side | side hint 必須 |
|
|
104
|
+
|
|
105
|
+
### 4. `state`
|
|
106
|
+
|
|
107
|
+
phase 進行で変わる動的値。 `node.value` / `node.rows` から `{stateId}` で参照。
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
diagram.state(id, { initial: number | string });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
例 ... `aliceBalance` (initial 100) / `bobBalance` (initial 0)。
|
|
114
|
+
|
|
115
|
+
### 5. `phase`
|
|
116
|
+
|
|
117
|
+
時系列 step。 activate (光らせる) と tween (state を補間) を 1 ブロックで宣言。
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
diagram.phase(id, {
|
|
121
|
+
duration: number, // ms (default 900)
|
|
122
|
+
title: string, // 現在 phase 大タイトル (日本語、 header に表示)
|
|
123
|
+
body: string, // 説明文 (日本語、 header に表示)
|
|
124
|
+
}, builder => builder
|
|
125
|
+
.activate(...ids) // active な node / edge id を列挙
|
|
126
|
+
.tween(stateId, from, to) // state を線形補間
|
|
127
|
+
.badge(text) // BalanceDiffCard 等の status badge 文字列
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
phase が定義されると自動的に。
|
|
132
|
+
- 全 phase が timeline 化 (autoplay + loop)
|
|
133
|
+
- 各 phase の active set 以外は inactive 化 (灰 dashed)
|
|
134
|
+
- state が tween 指定された範囲で値を更新
|
|
135
|
+
- header に現在 phase の title / body / badge を表示
|
|
136
|
+
|
|
137
|
+
## Layout engine 規約
|
|
138
|
+
|
|
139
|
+
1. **lane** は宣言順に x 座標固定で並ぶ (重複可、 ただし overlap 検証で warn)
|
|
140
|
+
2. **node** は lane 内で stack 順に上から並ぶ、 各 stack の高さは kind の default (actor 140 / function 110 / storage 180 / event 130 / card 110)、 上下 gap 80px 固定
|
|
141
|
+
3. **lane label** は lane 上部に 30px の高さで表示
|
|
142
|
+
4. **contain: true** の lane は内部 node を 24px padding で破線 boundary 描く
|
|
143
|
+
5. **edge** routing は orthogonal L 字 (起点 side → 中継 90° → 終点 side)、 同 lane 跨ぎは S 字 1 本の bezier 許可
|
|
144
|
+
6. **edge label** は path 中点から「path 進行方向の垂直方向」 に 50-80px 離して配置、 lane / node bbox との overlap を計算で避ける
|
|
145
|
+
7. **viewBox** は cdl が auto 計算 (全 lane の右端 + 余白 80px、 全 node の下端 + 余白 80px)
|
|
146
|
+
|
|
147
|
+
## Routing v8 (fan-in / fan-out / label auto shift SSOT)
|
|
148
|
+
|
|
149
|
+
著者は座標 / offset / path 迂回を書かない。 以下 5 保証は全て engine が担う (`packages/cdl/src/layout/edges.ts` + `label-shift.ts` + `collisions.ts` 集約)。
|
|
150
|
+
|
|
151
|
+
1. **fan-in 分散** ... 同 to node に集約する複数 edge を to 側 sidepoint offset で分散、 path 重複 0
|
|
152
|
+
2. **fan-out 分散** ... 同 from node からの複数 edge を to の cy 順で offset 分散、 交差 0
|
|
153
|
+
3. **WORLD_SCALE = 4** ... engine world 座標系と DOM 描画 px スケールの乖離を吸収する経験係数、 `labelBoxW * WORLD_SCALE + CLEARANCE_NODE_LABEL * 2 * WORLD_SCALE` で obstacle 間 gap を測る (`edges.ts` 373 行 SSOT)
|
|
154
|
+
4. **水平大回り detour** ... A / B 間の水平 gap が label 幅 + clearance より狭く obstacle で挟まれた場合、 verticalMidY を obstacle 帯を避ける y に調整 + midX を label 収納可能位置に押し出す
|
|
155
|
+
5. **label auto shift v5** ... 格子探索 + 字幅精度 + score min 選択で label bbox を node / 他 label と衝突しない位置に自動配置、 著者が `labelOffsetX/Y` を書くと engine 探索より優先されるが、 通常は engine 任せで良い
|
|
156
|
+
|
|
157
|
+
border case sweep test (`dragon apps/playground/tests/visual/overlap-detector.spec.ts`) は上記 5 保証を DOM `getBoundingClientRect` 実測で検証し、 1 件でも overlap 検出で fail する。 allowlist は空 (`BORDER_CASE_DIAGRAMS = new Set<string>()`)、 全 diagram が engine 層で解消する規約。
|
|
158
|
+
|
|
159
|
+
### v9 追加保証 = 空 label edge の DOM 描画 skip
|
|
160
|
+
|
|
161
|
+
6. **空 label DOM skip** ... `edge.label` / `edge.sub` 両方が空文字なら、 `label-shift.ts` の bbox 生成対象外 (PR #48) に加えて `CdlEdgeLabelView` render 層でも `null` 返して DOM element 自体を生成しない (`packages/cdl/src/render/edges.tsx` SSOT)。 空 label でも rect + text が描画されると 12.5x12.5 の小 marker として残り、 sweep test で近位 node bbox と 数十 px² の実測 overlap を生む (mind-demo で 61px² 事象、 cdl PR #55)。
|
|
162
|
+
|
|
163
|
+
### v10 追加保証 = 中央 anchor hard 制約 + TARGET_DIST score 化
|
|
164
|
+
|
|
165
|
+
7. **中央 anchor hard 制約** ... label 位置は edge path の中央 t=0.5 ± 20% + normal ± 30° の tangent-normal frame 内を優先 (`label-shift.ts` `pathCenterFrame` + `isLabelInCenterAnchor` SSOT)。 端寄り / 側面へ飛ぶ候補を Tier 0 で除外し、 label が edge の「意味的中央」 に配置されることを保証。 topo-demo の round-robin label が edge 上に載る症状 + flow-demo の label 下辺 arrow marker 密着を解消 (PR #57)。
|
|
166
|
+
8. **fan-in t 分散** ... 同 to node に集約する複数 edge の label は path 中央 t=0.5 ± 0.15 * (idx - centerIdx) で t 分散 (`computeFanTShift` SSOT)、 pattern-fan-in の 3 「result」 label 集中を解消。 fan-in と fan-out どちらか一意の側でのみ発火 (両方 2 本以上なら分散なし)。
|
|
167
|
+
9. **TARGET_DIST score 化** ... label 位置 score を「path 近接優先」 (dist * 2) から「NORMAL 目標距離 100 world (DOM ~25 px) との差 min」 (`Math.abs(dist - TARGET_DIST) * 2 + shift * 0.05 + label.y * 0.01`) に転換。 path から離れすぎず近すぎず の中間位置を選ぶ (旧 dist*2 は label が path に載る原因、 PR #57)。
|
|
168
|
+
|
|
169
|
+
### v10.1 追加保証 = detour gap 拡張 + slot 別 detour Y 分散
|
|
170
|
+
|
|
171
|
+
10. **detour gap 拡張 (44 → 94、 +50)** ... label-shift v10 が detour path から NORMAL 100 world 上に label を置く挙動と組み合わせると、 obstacle.bottom + clearance 32 world では label bottom が obstacle と 24 world 割り込む (pattern-rollback e3 の label × commit 20 px² overlap の root cause、 cdl PR #58)。 gap +50 で label 位置が obstacle bottom から 66 world (DOM ~16 px) 離れる。
|
|
172
|
+
11. **fan-in slot 別 detour Y 分散** ... 同 to node の複数 detour edge (pattern-rollback の e3 + e5 が両方 → db) が同 y 帯集約して label × label collision する問題を解消。 `slotFromCenter = Math.round(offset / fanGap)` で slot index を復元、 slot 0 起点で obstacle 側から離れる方向にのみ 0..fanGap の絶対値 shift。 上迂回 case は逆方向 (上方) にのみ shift、 detour Y 符号に合わせて分散方向を決定 (`edges.ts` `isDownDetour ? +detourYAdjustAbs : -detourYAdjustAbs` SSOT)。
|
|
173
|
+
|
|
174
|
+
### routing SSOT 前提 (壊れると再検証必要)
|
|
175
|
+
|
|
176
|
+
以下の 3 前提を維持する限り上記 11 保証が有効:
|
|
177
|
+
|
|
178
|
+
- **dragon CdlDiagramThumbnail の display 幅** ... 490 px 相当 (SVG viewBox 1820 world unit と scale 0.269 で対応)
|
|
179
|
+
- **diagram viewBox 幅** ... 1500-2000 world unit 相当 (author lane 定数 SSOT の慣例値)
|
|
180
|
+
- **font family** ... Inter / Newsreader (measureTextWidth の pt 想定と一致)
|
|
181
|
+
|
|
182
|
+
これらが変更されたら `WORLD_SCALE = 4.0` (v8 の 3 番) と `TARGET_DIST = 100` (v10 の 9 番) の実測 SSOT を再測定して係数を再チューニングする (`edges.ts` v8 SSOT comment + `label-shift.ts` v10 SSOT comment 参照)。
|
|
183
|
+
|
|
184
|
+
## Chain 伝搬 shift + spec 固定化 SSOT (CAR-418 系)
|
|
185
|
+
|
|
186
|
+
routing v8 / v10 系の場当たり heuristics を段階的に「宣言的 spec + chain 伝搬」 に置換する SSOT。 dagre / elk など完全 auto layout の外部 lib は範囲外、 cdl 内部で解決する哲学を維持する (§ v1 適用範囲 と一致)。
|
|
187
|
+
|
|
188
|
+
### 二層 SSOT (positive spec と near-collision の分離)
|
|
189
|
+
|
|
190
|
+
layout constants は 2 種類の SSOT で管理する、 いずれも `packages/cdl/src/layout/spec.ts` に集約 (CAR-424)。
|
|
191
|
+
|
|
192
|
+
| SSOT | 対象 | 用途 | dict |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| **positive spec** | engine が満たすべき正しい配置目標値 | `visualValidateLaid` axes / dragon pixel-perfect gate | `SPEC_CLEARANCE_POLICY` |
|
|
195
|
+
| **near-collision** | 「これ以下は誤読」 の下限 | `detectNearCollisions` (`collisions.ts`) | `NEAR_COLLISION_POLICY` |
|
|
196
|
+
|
|
197
|
+
primitive 定数 (`CLEARANCE_NODE_LABEL` / `CLEARANCE_LANE_LABEL` / `CLEARANCE_LABEL_LABEL` / `CLEARANCE_PATH_LABEL` / `TARGET_LABEL_PATH_DIST` 等) は `packages/cdl/src/layout/clearance-constants.ts` に置き、 spec.ts / collisions.ts / label-shift.ts / edges.ts の全 downstream が import で参照する。 CAR-424 で `edges.ts` / `label-shift.ts` の module-local 定数を全削除、 `clearance-constants.ts` を 1 箇所 SSOT に集約した。
|
|
198
|
+
|
|
199
|
+
### 現行 pipeline と CAR-418 移行状態
|
|
200
|
+
|
|
201
|
+
現在 (2026-07-04) の `layout()` pipeline (`packages/cdl/src/layout.ts`) は以下 6 phase を走る。
|
|
202
|
+
|
|
203
|
+
1. `layoutLanes` → `layoutNodes` → `expandLanesForNodes` (lane / node 静的配置)
|
|
204
|
+
2. `layoutEdges` (edge routing、 fan-in / fan-out offset 分散 + orthogonal routing + 水平大回り detour + midX/midY retreat)
|
|
205
|
+
3. `detectCollisions` → `resolveOverlaps` (node-node 後勝ち一発 shift)
|
|
206
|
+
4. `layoutEdges` (moved node で edge 再 route)
|
|
207
|
+
5. `shiftLabelsAwayFromNodes` (label auto shift、 16 方向 × 26 段階 + 中央 anchor Tier)
|
|
208
|
+
6. `computeViewBox` (viewport 決定)
|
|
209
|
+
|
|
210
|
+
CAR-421 で `resolveOverlapsWithChain(nodes, minClearance, maxIter)` を新設し、 node-node overlap の chain 伝搬 shift を実装した (`packages/cdl/src/layout/collisions.ts`)。 単体 API として export 済、 実 pipeline `layout()` は現状 `resolveOverlaps` のまま (sequence preset の lifeline 等の既存 snapshot 保持のため opt-in で呼ぶ設計、 全 preset での有効化は将来 Issue で段階展開)。
|
|
211
|
+
|
|
212
|
+
edge 路線分散 (fan-in / fan-out offset) と label auto shift (16 方向 × 26 段階) は現在も稼働している。 これらを chain 伝搬に一本化する構想は CAR-418 で提示済だが、 label / edge の chain 伝搬 shift 実装は node-node の resolveOverlapsWithChain より複雑 (obstacle chain が edge path / label bbox / fan slot の 3 軸で干渉する)、 段階的 PR で扱う (段階展開 SSOT は CAR-418 親 Issue)。
|
|
213
|
+
|
|
214
|
+
### CAR-424 の完了範囲 (二層 SSOT 集約 + SPEC 明文化)
|
|
215
|
+
|
|
216
|
+
CAR-424 は「PR 3 = 旧場当たり logic 除去 + SPEC.md 更新」 の scope だが、 現行 routing v8 / v10 系は border case sweep test (dragon overlap-detector) + 15+ preset の snapshot で品質保証されている。 場当たり logic の一括削除は「chain 伝搬 shift を label / edge にも展開」 が完了してからでないと preset 品質を落とすため、 本 PR では以下 3 点に scope を限定する。
|
|
217
|
+
|
|
218
|
+
- **module-local 重複定数の集約** ... `edges.ts` `CLEARANCE_NODE_LABEL = 32` と `label-shift.ts` の 4 clearance 定数を `clearance-constants.ts` import に置換 (dedup)
|
|
219
|
+
- **`CLEARANCE_POLICY` の SSOT 集約** ... `collisions.ts` module-local `CLEARANCE_POLICY` dict を `spec.ts` `NEAR_COLLISION_POLICY` に移管、 `collisions.ts requiredClearance` は `requiredNearClearance` に委譲する薄い adapter に縮小
|
|
220
|
+
- **本 SPEC.md 「Chain 伝搬 shift + spec 固定化 SSOT」 セクション追加** ... 二層 SSOT (positive spec / near-collision) 分離、 現行 pipeline の CAR-418 移行状態、 CAR-424 完了範囲を明文化
|
|
221
|
+
|
|
222
|
+
`edges.ts` fan-in / fan-out offset 分散 の削除 + `label-shift.ts` shift 探索 削除 は「chain 伝搬 shift を label / edge にも展開」 が別 Issue で完了してから段階削除、 現行の場当たり logic の SSOT comment (`edges.ts` v8 SSOT + `label-shift.ts` v10 SSOT) は据え置き。
|
|
223
|
+
|
|
224
|
+
### 参照
|
|
225
|
+
|
|
226
|
+
- 実装 SSOT ... `packages/cdl/src/layout/spec.ts` (二層 policy dict + positive spec 定数) + `packages/cdl/src/layout/clearance-constants.ts` (primitive 定数)
|
|
227
|
+
- chain 伝搬 API ... `resolveOverlapsWithChain(nodes, minClearance, maxIter)` (`collisions.ts`)
|
|
228
|
+
- 親 Issue ... CAR-418 (chain 伝搬 shift + spec 固定化 で edge geometry 完全化)
|
|
229
|
+
- 段階 PR ... CAR-421 (PR 1、 spec.ts SSOT + chain 伝搬 shift + positive-check axis 5)、 CAR-422 (PR 2、 positive-check axis 5 拡張)、 CAR-424 (PR 3、 本 SPEC 明文化 + 定数 dedup)
|
|
230
|
+
|
|
231
|
+
## Phase 進行規約
|
|
232
|
+
|
|
233
|
+
- autoplay default true、 loop default true
|
|
234
|
+
- phaseHoldMs = 900、 loopRestartDelayMs = 1400 (cdl default)
|
|
235
|
+
- 各 phase の duration は引数指定、 default 900ms
|
|
236
|
+
- 各 phase の active set 以外は opacity 0.5 + 灰 stroke + dasharray `3 4`
|
|
237
|
+
- contract boundary は phase 共通で薄く描く (opacity 0.55 + dasharray `8 6`)
|
|
238
|
+
- state tween は phase 開始から duration まで線形補間、 progress 0..1 を view が読む
|
|
239
|
+
|
|
240
|
+
## Header 規約
|
|
241
|
+
|
|
242
|
+
cdl render は diagram の上に Header を自動配置。
|
|
243
|
+
|
|
244
|
+
- 左 ... 「ERC-20 Transfer (diagram id 由来)」 eyebrow + step indicator (4 dot、 active 22px pill)
|
|
245
|
+
- 中央 ... 現在 phase の step 番号 chip + title + body
|
|
246
|
+
- 右 ... BalanceDiffCard (state を持つ diagram のみ)
|
|
247
|
+
|
|
248
|
+
著者が Header を編集する手段はなし (規約固定で UI ぶれを防ぐ)。
|
|
249
|
+
|
|
250
|
+
## File 配置
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
packages/cdl/
|
|
254
|
+
├── SPEC.md ... 本 file (SSOT)
|
|
255
|
+
├── package.json
|
|
256
|
+
├── tsconfig.json
|
|
257
|
+
└── src/
|
|
258
|
+
├── index.ts ... public API entry
|
|
259
|
+
├── builder.ts ... fluent API (diagram / lane / node / edge / state / phase)
|
|
260
|
+
├── types.ts ... CdlDiagram / CdlNode / CdlEdge / CdlPhase 型
|
|
261
|
+
├── layout.ts ... lane based 配置 + orthogonal routing
|
|
262
|
+
├── compile.ts ... builder → 静的 layout + Timeline 引数を計算
|
|
263
|
+
├── render.tsx ... compile 結果を React + SVG で描画 (+ @cardenelabs/anim Timeline 接続)
|
|
264
|
+
├── kinds/
|
|
265
|
+
│ ├── actor.tsx
|
|
266
|
+
│ ├── function.tsx
|
|
267
|
+
│ ├── storage.tsx
|
|
268
|
+
│ ├── event.tsx
|
|
269
|
+
│ └── card.tsx
|
|
270
|
+
└── validate.ts ... 6 軸の事前検証 (overlap / arrow consistency / japanese-only 等)
|
|
271
|
+
|
|
272
|
+
apps/web/src/topics/erc20-transfer.cdl.ts ... 著者の入口 (cdl で記述)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Examples
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// apps/web/src/topics/erc20-transfer.cdl.ts
|
|
279
|
+
import { diagram } from "@cardenelabs/cdl";
|
|
280
|
+
|
|
281
|
+
export const erc20Transfer = diagram("erc20-transfer", { topic: "ERC-20 Transfer" })
|
|
282
|
+
.lane("actors", { x: 0, width: 320, label: "外部主体" })
|
|
283
|
+
.lane("contract", { x: 360, width: 760, label: "ERC-20 コントラクト", contain: true })
|
|
284
|
+
.lane("output", { x: 1160, width: 280, label: "外部通知" })
|
|
285
|
+
|
|
286
|
+
.state("aliceBalance", { initial: 100 })
|
|
287
|
+
.state("bobBalance", { initial: 0 })
|
|
288
|
+
|
|
289
|
+
.node("alice", { lane: "actors", stack: 0, kind: "actor",
|
|
290
|
+
title: "Alice", eyebrow: "送信元", value: "{aliceBalance}" })
|
|
291
|
+
.node("bob", { lane: "actors", stack: 2, kind: "actor",
|
|
292
|
+
title: "Bob", eyebrow: "受け取り側", value: "{bobBalance}" })
|
|
293
|
+
.node("fn", { lane: "contract", stack: 0, kind: "function",
|
|
294
|
+
title: "transfer(Bob, 10)",
|
|
295
|
+
eyebrow: "コントラクト関数",
|
|
296
|
+
subtitle: "実行者 (msg.sender) は Alice" })
|
|
297
|
+
.node("storage", { lane: "contract", stack: 1, kind: "storage",
|
|
298
|
+
title: "balances",
|
|
299
|
+
eyebrow: "保存データの定義",
|
|
300
|
+
rows: ["Alice: {aliceBalance}", "Bob: {bobBalance}"] })
|
|
301
|
+
.node("event", { lane: "output", stack: 0, kind: "event",
|
|
302
|
+
title: "Transfer", subtitle: "(Alice, Bob, 10)" })
|
|
303
|
+
|
|
304
|
+
.edge("alice", "fn", { id: "call", label: "呼び出し", sub: "transfer(Bob, 10)", tone: "accent" })
|
|
305
|
+
.edge("fn", "storage", { id: "read", label: "読み取り", sub: "balances[Alice]", tone: "teal", side: "top" })
|
|
306
|
+
.edge("fn", "storage", { id: "write", label: "書き込み", sub: "balances を更新", tone: "accent", side: "bottom" })
|
|
307
|
+
.edge("fn", "event", { id: "emit", label: "通知", sub: "emit Transfer", tone: "success" })
|
|
308
|
+
|
|
309
|
+
.phase("call", { duration: 900, title: "Alice がトランザクションを送る",
|
|
310
|
+
body: "署名済みトランザクションが ERC-20 コントラクトへ届きます。 まだ残高は変わりません。" },
|
|
311
|
+
p => p.activate("alice", "call").badge("依頼を受け取る"))
|
|
312
|
+
|
|
313
|
+
.phase("read", { duration: 900, title: "残高を確認する",
|
|
314
|
+
body: "transfer 関数が balances[Alice] を読み、 10 トークン以上持っているかを判定します。" },
|
|
315
|
+
p => p.activate("fn", "storage", "read").badge("保存データを読み取り"))
|
|
316
|
+
|
|
317
|
+
.phase("write", { duration: 1100, title: "保存データを書き換える",
|
|
318
|
+
body: "balances[Alice] を -10、 balances[Bob] を +10。 ここで初めて状態 (真の残高) が動きます。" },
|
|
319
|
+
p => p.activate("fn", "storage", "write")
|
|
320
|
+
.tween("aliceBalance", 100, 90)
|
|
321
|
+
.tween("bobBalance", 0, 10)
|
|
322
|
+
.badge("保存データを更新済み"))
|
|
323
|
+
|
|
324
|
+
.phase("emit", { duration: 900, title: "Transfer イベントを発火",
|
|
325
|
+
body: "Transfer(from, to, value) を emit。 ウォレットやブロックエクスプローラはこの log で履歴を追います。" },
|
|
326
|
+
p => p.activate("fn", "event", "emit", "bob").badge("イベントを通知"));
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Validation 6 軸 (cdl build 時に走る)
|
|
330
|
+
|
|
331
|
+
`diagram.compile()` の前に `validate(diagram)` を回し、 以下を機械検証する。
|
|
332
|
+
|
|
333
|
+
| 軸 | 検出 |
|
|
334
|
+
|---|---|
|
|
335
|
+
| overlap | lane bbox 重なり、 node bbox 重なり、 edge と node の侵食 |
|
|
336
|
+
| arrow | edge の from/to が存在、 同 from/to ペアが side hint なく被る |
|
|
337
|
+
| label | label / sublabel 文字数で box が lane 幅を超える |
|
|
338
|
+
| japanese-only | title / eyebrow / subtitle / label に uppercase 英単語 (FUNCTION/STORAGE/...) |
|
|
339
|
+
| phase-isolation | phase の activate id が存在する node/edge を指す |
|
|
340
|
+
| neumorphism | tone が定義された 4 色から外れる |
|
|
341
|
+
|
|
342
|
+
build error / warn を投げ、 著者は cdl 段階で修正できる (screenshot 取る前に)。
|
|
343
|
+
|
|
344
|
+
## v1 で対応しないもの (将来)
|
|
345
|
+
|
|
346
|
+
- node の自由配置 (lane / stack の宣言型のみ)、 free coordinate は escape hatch `raw(svg)` のみ
|
|
347
|
+
- 同 lane 内 stack の自動上下入替 (manual stack 番号で固定)
|
|
348
|
+
- mermaid 互換 text DSL (将来 `parse(text)` を追加可、 v1 は TS API のみ)
|
|
349
|
+
- 多階層 lane (lane の入れ子) ... v1 は flat のみ、 必要なら kind: storage を分解
|
|
350
|
+
|
|
351
|
+
## v1 適用範囲 (検証で確定)
|
|
352
|
+
|
|
353
|
+
cdl の宣言型 + lane based engine は次のトポロジーが得意。
|
|
354
|
+
|
|
355
|
+
| パターン | 例 | 適用 |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| **線形 chain** (3-5 phase で逐次進行) | erc20-transfer (call→read→write→emit) | ✅ 完璧 |
|
|
358
|
+
| **2-3 lane 横並び** + 各 lane 1-2 nodes | erc20 (actors / contract / output) | ✅ 完璧 |
|
|
359
|
+
| **fan-out (1 node → 多 to)** で targets が 1 lane に集約 | erc20 fn → ownerStore (read/write 2 edge) | ✅ port 分散で対応 |
|
|
360
|
+
| **fan-out で targets が複数 lane に散らばる** | erc721 fn → hook + event + ownerStore (3 to in 3 lanes) | ⚠️ engine 限界、 path 干渉発生 |
|
|
361
|
+
| **cycle (循環)** | A → B → C → A | ❌ v1 未対応、 stack 順序で不可 |
|
|
362
|
+
| **多階層 (lane 入れ子)** | UML 風 swimlane | ❌ v1 flat のみ |
|
|
363
|
+
|
|
364
|
+
### 著者向けガイドライン
|
|
365
|
+
|
|
366
|
+
トピックが「fan-out 多 lane」 に該当する場合の回避策。
|
|
367
|
+
|
|
368
|
+
1. **構造を線形化** ... fan-out を「順次 chain」 に書き換え (例 hook 呼出を read-owner / write-owner の間の独立 phase として表現、 edge は fn → hook の 1 本のみ)
|
|
369
|
+
2. **edge 数を絞る** ... 1 node から出す edge は **最大 2-3 本** に抑える
|
|
370
|
+
3. **不要 node を別 phase の body 説明に逃がす** ... 例えば hook が phase 説明文で十分なら node 化しない
|
|
371
|
+
4. **複雑トピックは複数の小 diagram に分割** ... 1 diagram = 1 主張、 で書く
|
|
372
|
+
|
|
373
|
+
cdl の哲学 = 「人が宣言で書ける範囲を engine が綺麗に整える」、 完全 auto layout (dagre / elkjs) は範囲外。
|
|
374
|
+
複雑グラフは複数 diagram に分けて読み手に提示する方が学習効果も高い。
|