@cardenelabs/cdl 0.40.12 → 0.41.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.40.12",
3
+ "version": "0.41.1",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -325,7 +325,6 @@ export type VisualAxis =
325
325
  | "row-content-typing"
326
326
  | "terminal-safe-text"
327
327
  | "gpu-layer-efficiency"
328
- | "memory-budget"
329
328
  // CAR-Z8 (PR #87) 追加 = XSS / SEO / bidi / structured-data 4 領域。
330
329
  | "svg-injection-safety"
331
330
  | "seo-metadata-quality"
@@ -479,7 +478,6 @@ function emptyCounts(): Record<VisualAxis, number> {
479
478
  "row-content-typing": 0,
480
479
  "terminal-safe-text": 0,
481
480
  "gpu-layer-efficiency": 0,
482
- "memory-budget": 0,
483
481
  "svg-injection-safety": 0,
484
482
  "seo-metadata-quality": 0,
485
483
  "bidi-hyphenation": 0,
@@ -1219,8 +1217,11 @@ export function countEdgeCrossings(
1219
1217
  * 分割片どうしの比較回数の上限。
1220
1218
  *
1221
1219
  * 辺の総当たりで、 各対の分割片を全比較する。 曲線 380 本の図では 1850 万回になり 264ms
1222
- * かかった (実測)。 1 図あたりの予算は `PERF_BUDGET_PER_DIAGRAM_MS` = 100 で、 他の軸が
1223
- * 150ms 前後使うため本軸に使えるのは僅か。
1220
+ * かかった (実測)。 他の軸が 1 図あたり 150ms 前後を使うため、 本軸に回せるのは僅か。
1221
+ *
1222
+ * **1 図あたりの上限では判定していない** (`#776`)。 壁時計は同じ図を 3 周しても
1223
+ * 53 / 0 / 1 ms と揺れるので、 判定は全図の合計 (`PERF_BUDGET_TOTAL_MS`) だけが持つ。
1224
+ * ここの上限は「1 図が合計を食い潰さない」 ための設計上の目安で、 判定の境ではない。
1224
1225
  *
1225
1226
  * **上限に達したら数えるのをやめる**。 包囲箱で絞れないのは曲線どうしが広く重なる図で、
1226
1227
  * その形は総当たり以外に手が無い。 打ち切ると警告が出ないが、 出ない状態は弦で数えていた
@@ -3630,32 +3631,6 @@ function runAxes(
3630
3631
  );
3631
3632
  }
3632
3633
 
3633
- // ───────────────────────────────────────────────────────────
3634
- // Axis 40: memory-budget (diagram size 総和による React re-render 負荷)
3635
- //
3636
- // 判定 = nodes×h×w + edges×path segment 数 + phases×(tweens + sets 数) の推定 memory 単位
3637
- // が MEMORY_BUDGET (10^7) を超えると React re-render で GC pressure。 catalog 内 diagram は
3638
- // 10^5-10^6 が実測、 10^7 で明確な破綻。
3639
- // ───────────────────────────────────────────────────────────
3640
- const MEMORY_BUDGET = 1_000_000;
3641
- let nodeMemUnit = 0;
3642
- for (const n of laid.nodes) nodeMemUnit += n.w * n.h;
3643
- let edgeMemUnit = 0;
3644
- for (const e of laid.edges) {
3645
- const segs = extractPathSegments(e.d);
3646
- edgeMemUnit += segs.length * 100; // 1 segment ≈ 100 memory unit (SVG path + marker + label)
3647
- }
3648
- let phaseMemUnit = 0;
3649
- for (const p of diag.phases) phaseMemUnit += (p.tweens.length + p.sets.length) * 50;
3650
- const totalMemUnit = nodeMemUnit + edgeMemUnit + phaseMemUnit;
3651
- if (totalMemUnit > MEMORY_BUDGET) {
3652
- push(
3653
- "memory-budget",
3654
- `diagram memory unit 推定 ${totalMemUnit.toFixed(0)} > budget ${MEMORY_BUDGET} (nodes=${nodeMemUnit.toFixed(0)} edges=${edgeMemUnit.toFixed(0)} phases=${phaseMemUnit.toFixed(0)})`,
3655
- "warn",
3656
- );
3657
- }
3658
-
3659
3634
  // ───────────────────────────────────────────────────────────
3660
3635
  // Axis 41: svg-injection-safety (XSS / script injection 検知、 error severity)
3661
3636
  //
@@ -4361,6 +4336,76 @@ export function visualValidateAll(diagrams: CdlDiagram[], opts?: ValidateOptions
4361
4336
  * source を読めない (build 後は minified) ため、 実装は要素数の一致に留まる。
4362
4337
  * `VisualAxis` / `emptyCounts` / `allAxes` の 3 つが揃っているかは見ない (#381)。
4363
4338
  */
4339
+ /** 全 diagram の検査に掛けてよい合計の時間 (ms)。 */
4340
+ export const PERF_BUDGET_TOTAL_MS = 5000;
4341
+
4342
+ /** 合計が境を超えた時に、内訳として名前を出す図の数。 */
4343
+ export const PERF_SLOWEST_SAMPLE = 5;
4344
+
4345
+ /**
4346
+ * Axis 52 `validate-performance-budget` の判定 (`#776`)。
4347
+ *
4348
+ * ## 1 枚ごとの壁時計では判定しない
4349
+ *
4350
+ * 同じ図を同じ process で 3 周すると答えが変わる。 実測 (下流の見本帳 444 枚)。
4351
+ *
4352
+ * ```
4353
+ * interactive-social-share-buttons 53ms / 0ms / 1ms
4354
+ * interactive-traffic-sankey 50ms / 159ms / 115ms
4355
+ * ```
4356
+ *
4357
+ * 1 つ目は最初の 1 回だけ掛かる下準備で、2 つ目は機械の混み具合。 どちらも図の性質ではない。
4358
+ * 100ms の境で数えると **入力が同じなのに超過が 0 / 1 / 1 件** と周ごとに変わっていた =
4359
+ * 測れなかったことを「遅い」 に倒していた
4360
+ * (`rules/quality.md § 判定できなかったことを値に潰さない`)。
4361
+ *
4362
+ * 合計は同じ 3 周で 559 / 877 / 858 ms と 1.6 倍の幅に収まり、境の 5000ms から充分離れている
4363
+ * ので判定が安定する。 **合計だけを見る**。
4364
+ *
4365
+ * ## 報告を 1 件に丸めない
4366
+ *
4367
+ * 合計だけを見ると「どの図が重いか」 が落ちるので、超過した時に重い順の内訳を文面へ載せる。
4368
+ * 出し切れない分は件数で残す = 黙って捨てない。
4369
+ *
4370
+ * 旧実装は `findIndex` で最初に当たった 1 枚しか出しておらず、20 枚が超過した周でも報告は
4371
+ * 1 枚だった。 残り 19 枚は数にも文面にも出ない。
4372
+ *
4373
+ * ## なぜ関数に切り出すか
4374
+ *
4375
+ * 合計が 5000ms を超える状況を実際に作ると、確かめる検査そのものが 5 秒以上掛かる。
4376
+ * 時間の配列を直接渡せる形にして、両向きを実時間なしで固定する。
4377
+ *
4378
+ * @param durations 図ごとの実行時間 (ms)。 `diagrams` と同じ並び
4379
+ * @param diagrams 図。 名前を内訳に出すためだけに使う
4380
+ */
4381
+ export function perfBudgetViolations(
4382
+ durations: readonly number[],
4383
+ diagrams: readonly { id: string }[] = [],
4384
+ ): Violation[] {
4385
+ const total = durations.reduce((s, d) => s + d, 0);
4386
+ if (total <= PERF_BUDGET_TOTAL_MS) return [];
4387
+
4388
+ const 重い順 = durations
4389
+ .map((ms, i) => ({ id: diagrams[i]?.id ?? "?", ms }))
4390
+ .sort((a, b) => b.ms - a.ms);
4391
+ const 抜粋 = 重い順.slice(0, PERF_SLOWEST_SAMPLE);
4392
+ const 残り = 重い順.length - 抜粋.length;
4393
+ const 内訳 =
4394
+ 抜粋.map((d) => `"${d.id}" ${d.ms}ms`).join(" / ") +
4395
+ (残り > 0 ? `、 残り ${残り} 図は出していない` : "");
4396
+
4397
+ return [
4398
+ {
4399
+ axis: "validate-performance-budget",
4400
+ diagramId: "__meta__",
4401
+ detail:
4402
+ `visualValidateAll 全 ${durations.length} 図の累計 ${total}ms > ` +
4403
+ `${PERF_BUDGET_TOTAL_MS}ms budget (重い順 ${内訳})`,
4404
+ severity: "warn",
4405
+ },
4406
+ ];
4407
+ }
4408
+
4364
4409
  function validateMetaAxes(
4365
4410
  totalCounts: Record<VisualAxis, number>,
4366
4411
  diagrams: CdlDiagram[] = [],
@@ -4392,27 +4437,7 @@ function validateMetaAxes(
4392
4437
  // 本 axis は 空 (engine 単体で判定材料なし)、 発火経路として先取り。
4393
4438
 
4394
4439
  // ─── Axis 52: validate-performance-budget (validate 実行時間) ───
4395
- const PERF_BUDGET_PER_DIAGRAM_MS = 100;
4396
- const PERF_BUDGET_TOTAL_MS = 5000;
4397
- const overBudgetIdx = durations.findIndex((d) => d > PERF_BUDGET_PER_DIAGRAM_MS);
4398
- if (overBudgetIdx >= 0) {
4399
- const did = diagrams[overBudgetIdx]?.id ?? "?";
4400
- out.push({
4401
- axis: "validate-performance-budget",
4402
- diagramId: did,
4403
- detail: `diagram "${did}" validate ${durations[overBudgetIdx]}ms > ${PERF_BUDGET_PER_DIAGRAM_MS}ms budget`,
4404
- severity: "warn",
4405
- });
4406
- }
4407
- const totalDur = durations.reduce((s, d) => s + d, 0);
4408
- if (totalDur > PERF_BUDGET_TOTAL_MS) {
4409
- out.push({
4410
- axis: "validate-performance-budget",
4411
- diagramId: "__meta__",
4412
- detail: `visualValidateAll 全 diagram 累計 ${totalDur}ms > ${PERF_BUDGET_TOTAL_MS}ms budget`,
4413
- severity: "warn",
4414
- });
4415
- }
4440
+ out.push(...perfBudgetViolations(durations, diagrams));
4416
4441
 
4417
4442
  // Axis 47: dead axis 検知
4418
4443
  // security / migration 系は自然発火なしを許容 (実データに XSS / migration 混入は稀)
@@ -4463,7 +4488,6 @@ function validateMetaAxes(
4463
4488
  "row-content-typing",
4464
4489
  "terminal-safe-text",
4465
4490
  "gpu-layer-efficiency",
4466
- "memory-budget",
4467
4491
  "svg-injection-safety",
4468
4492
  "seo-metadata-quality",
4469
4493
  "bidi-hyphenation",
@@ -4533,7 +4557,6 @@ function validateMetaAxes(
4533
4557
  "row-content-typing",
4534
4558
  "terminal-safe-text",
4535
4559
  "gpu-layer-efficiency",
4536
- "memory-budget",
4537
4560
  "svg-injection-safety",
4538
4561
  "seo-metadata-quality",
4539
4562
  "bidi-hyphenation",
@@ -4583,8 +4606,9 @@ function validateMetaAxes(
4583
4606
  // 以前は comment に 69、 定数に 68 と別の数が書かれており、 どちらが正か読めなかった。
4584
4607
  //
4585
4608
  // 削除済 = engine-meta 51 (mermaid-parity、 #366) / 軸 29 (svg-filter-integrity) /
4586
- // 軸 30 (neumorphism-shadow-budget、 どちらも #426 で主題の廃止に伴い削除)。
4587
- const EXPECTED_AXIS_COUNT = 68;
4609
+ // 軸 30 (neumorphism-shadow-budget、 どちらも #426 で主題の廃止に伴い削除) /
4610
+ // 軸 40 (memory-budget、 #775 で軸 34 に一本化)。
4611
+ const EXPECTED_AXIS_COUNT = 67;
4588
4612
  if (allAxes.length !== EXPECTED_AXIS_COUNT) {
4589
4613
  out.push({
4590
4614
  axis: "axis-documentation-completeness",