omkit 0.8.20 → 0.8.21

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.
@@ -34,14 +34,16 @@
34
34
  root: "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 2v3M12 19v3M2 12h3M19 12h3",
35
35
  chat: "M4 5h16v11H9l-5 4z",
36
36
  flask: "M9 3h6M10 3v6l-5 9a2 2 0 0 0 2 3h10a2 2 0 0 0 2-3l-5-9V3",
37
- braces: "M8 4c-2 0-3 1-3 3v2c0 1-1 2-2 3 1 1 2 2 2 3v2c0 2 1 3 3 3M16 4c2 0 3 1 3 3v2c0 1 1 2 2 3-1 1-2 2-2 3v2c0 2-1 3-3 3",
37
+ braces:
38
+ "M8 4c-2 0-3 1-3 3v2c0 1-1 2-2 3 1 1 2 2 2 3v2c0 2 1 3 3 3M16 4c2 0 3 1 3 3v2c0 1 1 2 2 3-1 1-2 2-2 3v2c0 2-1 3-3 3",
38
39
  image: "M4 5h16v14H4zM4 16l5-5 4 4 2-2 5 5M15 9h.01",
39
40
  sun: "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4",
40
41
  download: "M12 4v11M7 10l5 5 5-5M5 20h14",
41
42
  copy: "M9 9h11v11H9zM5 15V4h11",
42
43
  stop: "M6 6h12v12H6z",
43
44
  info: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 11v5M12 8h.01",
44
- locate: "M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM12 2v3M12 19v3M2 12h3M19 12h3M12 10.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z",
45
+ locate:
46
+ "M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM12 2v3M12 19v3M2 12h3M19 12h3M12 10.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z",
45
47
  code: "M8 7l-5 5 5 5M16 7l5 5-5 5",
46
48
  external: "M14 4h6v6M20 4l-9 9M18 14v6H4V6h6",
47
49
  };
@@ -83,7 +85,11 @@
83
85
  return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
84
86
  };
85
87
  const fmtSize = (b) =>
86
- b < 1024 ? `${b} B` : b < 1048576 ? `${Math.round(b / 1024)} KB` : `${(b / 1048576).toFixed(1)} MB`;
88
+ b < 1024
89
+ ? `${b} B`
90
+ : b < 1048576
91
+ ? `${Math.round(b / 1024)} KB`
92
+ : `${(b / 1048576).toFixed(1)} MB`;
87
93
  const fmtTick = (ms) => {
88
94
  if (ms === 0) return "0s";
89
95
  const a = Math.abs(ms);
@@ -92,9 +98,20 @@
92
98
  return `${Math.floor(ms / 60000)}m${pad2(Math.round((ms % 60000) / 1000))}s`;
93
99
  };
94
100
  const esc = (s) =>
95
- String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
101
+ String(s).replace(
102
+ /[&<>"']/g,
103
+ (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
104
+ );
96
105
  const basename = (p) => String(p).split(/[\\/]/).pop();
97
- const fileUrl = (p) => "file:///" + String(p).replace(/\\/g, "/").replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/").replace(/^([A-Za-z])%3A/, "$1:");
106
+ const fileUrl = (p) =>
107
+ "file:///" +
108
+ String(p)
109
+ .replace(/\\/g, "/")
110
+ .replace(/^\/+/, "")
111
+ .split("/")
112
+ .map(encodeURIComponent)
113
+ .join("/")
114
+ .replace(/^([A-Za-z])%3A/, "$1:");
98
115
  const editorUrl = (site) => {
99
116
  const m = /^(.*):(\d+)$/.exec(site);
100
117
  const file = (m ? m[1] : site).replace(/\\/g, "/");
@@ -127,7 +144,11 @@
127
144
  depth,
128
145
  branch: depth === 0 ? -1 : b,
129
146
  children: [],
130
- status: ["ok", "failed", "cancelled"].includes(v.status) ? v.status : v.endedAt ? v.status : "running",
147
+ status: ["ok", "failed", "cancelled"].includes(v.status)
148
+ ? v.status
149
+ : v.endedAt
150
+ ? v.status
151
+ : "running",
131
152
  startAbs: v.startedAt || t0,
132
153
  endAbs: v.endedAt || 0,
133
154
  entries: [],
@@ -153,7 +174,14 @@
153
174
  for (const e of data.entries) {
154
175
  if (e.level === "child" || e.level === "tag") continue;
155
176
  const kind = kindOf(e);
156
- const item = { idx: items.length, e, kind, t: e.ts - t0, label: labelOf(e, kind), node: byId.get(e.nodeId) || null };
177
+ const item = {
178
+ idx: items.length,
179
+ e,
180
+ kind,
181
+ t: e.ts - t0,
182
+ label: labelOf(e, kind),
183
+ node: byId.get(e.nodeId) || null,
184
+ };
157
185
  items.push(item);
158
186
  if (item.node) {
159
187
  item.node.entries.push(item);
@@ -174,12 +202,20 @@
174
202
  });
175
203
  }
176
204
  for (const s of data.snapshots) {
177
- gallery.push({ type: "snapshot", s, name: s.name, t: s.ts - t0, node: byId.get(s.nodeId) || null, image: false });
205
+ gallery.push({
206
+ type: "snapshot",
207
+ s,
208
+ name: s.name,
209
+ t: s.ts - t0,
210
+ node: byId.get(s.nodeId) || null,
211
+ image: false,
212
+ });
178
213
  }
179
214
  gallery.sort((a, b) => (a.t ?? Infinity) - (b.t ?? Infinity));
180
215
  // Let a mark find its gallery item (snapshot by file, artifact by file).
181
216
  const galleryByFile = new Map();
182
- for (const g of gallery) galleryByFile.set(String(g.type === "file" ? g.f.file : g.s.file).toLowerCase(), g);
217
+ for (const g of gallery)
218
+ galleryByFile.set(String(g.type === "file" ? g.f.file : g.s.file).toLowerCase(), g);
183
219
  for (const it of items) {
184
220
  if (it.kind === "snapshot" || it.kind === "artifact") {
185
221
  const i = it.e.message.lastIndexOf(" → ");
@@ -193,12 +229,31 @@
193
229
  const l = it.kind === "link" && it.node && it.e.link;
194
230
  const from = l && byId.get(l.from);
195
231
  if (!from) continue;
196
- links.push({ item: it, op: l.op, from, to: it.node, t: it.t, t0: it.t - (l.ms || 0), key: l.key || "", ms: l.ms, outcome: l.outcome || "", site: l.site || "" });
232
+ links.push({
233
+ item: it,
234
+ op: l.op,
235
+ from,
236
+ to: it.node,
237
+ t: it.t,
238
+ t0: it.t - (l.ms || 0),
239
+ key: l.key || "",
240
+ ms: l.ms,
241
+ outcome: l.outcome || "",
242
+ site: l.site || "",
243
+ });
197
244
  }
198
245
  // …drawn as a point on the row of the action that made it: what it did, when it did it
199
246
  for (const l of links) {
200
247
  l.label = linkLabel(l);
201
- l.from.marks.push({ idx: l.item.idx, e: l.item.e, kind: "link", t: l.t, label: l.label, node: l.from, link: l });
248
+ l.from.marks.push({
249
+ idx: l.item.idx,
250
+ e: l.item.e,
251
+ kind: "link",
252
+ t: l.t,
253
+ label: l.label,
254
+ node: l.from,
255
+ link: l,
256
+ });
202
257
  }
203
258
  for (const l of links) l.from.marks.sort((a, b) => a.t - b.t);
204
259
 
@@ -253,9 +308,23 @@
253
308
  const open = new Map(); // `${nodeId}\0${source}` → the output block still accepting lines
254
309
  for (const it of items) {
255
310
  if (it.kind === "fail") {
256
- out.push({ type: "assert", node: it.node, t: it.t, title: it.label, text: it.e.message, items: [it] });
311
+ out.push({
312
+ type: "assert",
313
+ node: it.node,
314
+ t: it.t,
315
+ title: it.label,
316
+ text: it.e.message,
317
+ items: [it],
318
+ });
257
319
  } else if (it.kind === "error" && it.e.source === "error") {
258
- out.push({ type: "failure", node: it.node, t: it.t, title: firstLine(it.e.message), text: it.e.message, items: [it] });
320
+ out.push({
321
+ type: "failure",
322
+ node: it.node,
323
+ t: it.t,
324
+ title: firstLine(it.e.message),
325
+ text: it.e.message,
326
+ items: [it],
327
+ });
259
328
  } else if (it.kind === "error") {
260
329
  const key = `${it.e.nodeId}\0${it.e.source}`;
261
330
  const block = open.get(key);
@@ -266,18 +335,38 @@
266
335
  block.text = block.lines.join("\n");
267
336
  continue;
268
337
  }
269
- const fresh = { type: "output", node: it.node, t: it.t, tEnd: it.t, source: it.e.source, title: firstLine(it.e.message), lines: [it.e.message], text: it.e.message, items: [it] };
338
+ const fresh = {
339
+ type: "output",
340
+ node: it.node,
341
+ t: it.t,
342
+ tEnd: it.t,
343
+ source: it.e.source,
344
+ title: firstLine(it.e.message),
345
+ lines: [it.e.message],
346
+ text: it.e.message,
347
+ items: [it],
348
+ };
270
349
  open.set(key, fresh);
271
350
  out.push(fresh);
272
351
  }
273
352
  }
274
353
  // a failure the verdict holds but no node logged (e.g. the body threw outside any action)
275
354
  for (const f of run.failures || []) {
276
- const n = nodes.find((x) => x.v.path === f.action || x.id === f.action || x.name === f.action) || null;
355
+ const n =
356
+ nodes.find((x) => x.v.path === f.action || x.id === f.action || x.name === f.action) ||
357
+ null;
277
358
  if (out.some((e) => e.type === "failure" && e.node === n)) continue;
278
359
  // a failed assert is already its own entry; the verdict just lists it again as a fault
279
- if (out.some((e) => e.type === "assert" && e.node === n && String(f.error).includes(e.title))) continue;
280
- out.push({ type: "failure", node: n, t: n ? n.end : span, title: firstLine(f.error), text: f.error, items: [] });
360
+ if (out.some((e) => e.type === "assert" && e.node === n && String(f.error).includes(e.title)))
361
+ continue;
362
+ out.push({
363
+ type: "failure",
364
+ node: n,
365
+ t: n ? n.end : span,
366
+ title: firstLine(f.error),
367
+ text: f.error,
368
+ items: [],
369
+ });
281
370
  }
282
371
  out.sort((a, b) => a.t - b.t);
283
372
  out.forEach((e, i) => (e.idx = i));
@@ -340,7 +429,8 @@
340
429
  details: true,
341
430
  };
342
431
  // Deep, wide runs start collapsed below the second level so the first screen stays readable.
343
- if (model.nodes.length > 60) for (const n of model.nodes) if (n.depth >= 2 && n.children.length) state.collapsed.add(n.id);
432
+ if (model.nodes.length > 60)
433
+ for (const n of model.nodes) if (n.depth >= 2 && n.children.length) state.collapsed.add(n.id);
344
434
 
345
435
  app.innerHTML = shellHtml(model);
346
436
  const el = {
@@ -368,9 +458,9 @@
368
458
  const xz = () => state.zoom.rescaleX(baseX);
369
459
 
370
460
  // ── header controls ──
371
- app.querySelectorAll("[data-view]").forEach((b) =>
372
- b.addEventListener("click", () => setView(b.dataset.view))
373
- );
461
+ app
462
+ .querySelectorAll("[data-view]")
463
+ .forEach((b) => b.addEventListener("click", () => setView(b.dataset.view)));
374
464
  app.querySelector("#zin").addEventListener("click", () => zoomBy(1.6));
375
465
  app.querySelector("#zout").addEventListener("click", () => zoomBy(1 / 1.6));
376
466
  app.querySelector("#fit").addEventListener("click", fit);
@@ -397,17 +487,26 @@
397
487
  };
398
488
  legendBtn.addEventListener("click", () => toggleLegend(legend.hidden));
399
489
  document.addEventListener("mousedown", (e) => {
400
- if (!legend.hidden && !legend.contains(e.target) && !legendBtn.contains(e.target)) toggleLegend(false);
490
+ if (!legend.hidden && !legend.contains(e.target) && !legendBtn.contains(e.target))
491
+ toggleLegend(false);
401
492
  });
402
- document.addEventListener("keydown", (e) => e.key === "Escape" && !legend.hidden && toggleLegend(false));
493
+ document.addEventListener(
494
+ "keydown",
495
+ (e) => e.key === "Escape" && !legend.hidden && toggleLegend(false)
496
+ );
403
497
 
404
498
  // ── zoom behaviours ──
405
499
  const chartZoom = d3
406
500
  .zoom()
407
501
  .scaleExtent([1, 5000])
408
- .filter((e) => (e.type === "wheel" ? e.ctrlKey || e.metaKey : !e.button && e.type !== "dblclick"))
502
+ .filter((e) =>
503
+ e.type === "wheel" ? e.ctrlKey || e.metaKey : !e.button && e.type !== "dblclick"
504
+ )
409
505
  .on("zoom", (e) => onZoom(e.transform));
410
- const flowZoom = d3.zoom().scaleExtent([0.1, 4]).on("zoom", (e) => flowG && flowG.attr("transform", e.transform));
506
+ const flowZoom = d3
507
+ .zoom()
508
+ .scaleExtent([0.1, 4])
509
+ .on("zoom", (e) => flowG && flowG.attr("transform", e.transform));
411
510
 
412
511
  function activeZoom() {
413
512
  return { z: chartZoom, sel: d3.select(el.chart).select("svg") };
@@ -417,7 +516,13 @@
417
516
  redrawX();
418
517
  }
419
518
  function zoomBy(k) {
420
- if (state.view === "flow") return d3.select(el.flow).select("svg").transition().duration(200).call(flowZoom.scaleBy, k);
519
+ if (state.view === "flow")
520
+ return d3
521
+ .select(el.flow)
522
+ .select("svg")
523
+ .transition()
524
+ .duration(200)
525
+ .call(flowZoom.scaleBy, k);
421
526
  const { z, sel } = activeZoom();
422
527
  const [cx] = state.pin != null ? [xz()(state.pin)] : [W / 2];
423
528
  sel.transition().duration(200).call(z.scaleBy, k, [cx, 0]);
@@ -443,13 +548,26 @@
443
548
  el.head.style.paddingRight = `${sb}px`;
444
549
  W = Math.max(200, el.chart.clientWidth);
445
550
  const span = model.span;
446
- baseX = d3.scaleLinear().domain([-span * 0.01, span * 1.015]).range([0, W]);
447
- chartZoom.translateExtent([[0, -1e6], [W, 1e6]]).extent([[0, 0], [W, 1]]);
551
+ baseX = d3
552
+ .scaleLinear()
553
+ .domain([-span * 0.01, span * 1.015])
554
+ .range([0, W]);
555
+ chartZoom
556
+ .translateExtent([
557
+ [0, -1e6],
558
+ [W, 1e6],
559
+ ])
560
+ .extent([
561
+ [0, 0],
562
+ [W, 1],
563
+ ]);
448
564
  }
449
565
 
450
566
  function setView(v) {
451
567
  state.view = v;
452
- app.querySelectorAll("[data-view]").forEach((b) => b.classList.toggle("active", b.dataset.view === v));
568
+ app
569
+ .querySelectorAll("[data-view]")
570
+ .forEach((b) => b.classList.toggle("active", b.dataset.view === v));
453
571
  el.body.hidden = v !== "timeline";
454
572
  el.head.hidden = v === "flow";
455
573
  el.timeline.classList.toggle("is-flow", v === "flow");
@@ -498,7 +616,9 @@
498
616
  <span class="icon" style="color:${color};background:${alpha(color, 0.16)}">${svgIcon(iconOf(n))}</span>
499
617
  <span class="lines">
500
618
  <span class="l1"><span class="label">${labelHtml(n)}</span>${
501
- model.errorsBy.get(n) ? `<span class="errbadge" title="${model.errorsBy.get(n)} error${model.errorsBy.get(n) > 1 ? "s" : ""}">${model.errorsBy.get(n)}</span>` : ""
619
+ model.errorsBy.get(n)
620
+ ? `<span class="errbadge" title="${model.errorsBy.get(n)} error${model.errorsBy.get(n) > 1 ? "s" : ""}">${model.errorsBy.get(n)}</span>`
621
+ : ""
502
622
  }</span>
503
623
  <span class="l2">${tags}<span class="meta"><span class="st"><span class="dot ${n.status}"></span>${esc(n.status)}</span><span class="sep">·</span><span class="du">${fmtDur(n.duration)}</span></span></span>
504
624
  </span>
@@ -529,8 +649,19 @@
529
649
  chartSvg.append("g").attr("class", "links");
530
650
  chartSvg.append("g").attr("class", "xwires");
531
651
  chartSvg.append("g").attr("class", "rows");
532
- chartSvg.append("line").attr("class", "pin").attr("stroke", css("--accent")).attr("stroke-width", 1.5).attr("y1", 0).attr("y2", H);
533
- chartSvg.append("line").attr("class", "cursor").attr("y1", 0).attr("y2", H).attr("visibility", "hidden");
652
+ chartSvg
653
+ .append("line")
654
+ .attr("class", "pin")
655
+ .attr("stroke", css("--accent"))
656
+ .attr("stroke-width", 1.5)
657
+ .attr("y1", 0)
658
+ .attr("y2", H);
659
+ chartSvg
660
+ .append("line")
661
+ .attr("class", "cursor")
662
+ .attr("y1", 0)
663
+ .attr("y2", H)
664
+ .attr("visibility", "hidden");
534
665
  chartSvg.call(chartZoom).call(chartZoom.transform, state.zoom);
535
666
  redrawX();
536
667
  }
@@ -581,9 +712,18 @@
581
712
  .join((enter) => {
582
713
  const r = enter.append("g").attr("class", "r");
583
714
  // A transparent target spanning the bar *and* its label, so a 2px bar is still hoverable.
584
- r.append("rect").attr("class", "hit").attr("y", BAR_Y - 4).attr("height", BAR_H + 8);
585
- r.append("rect").attr("class", "bar").attr("rx", 5).attr("y", BAR_Y).attr("height", BAR_H);
586
- r.append("text").attr("class", "bar-label").attr("y", BAR_Y + BAR_H / 2 + 4);
715
+ r.append("rect")
716
+ .attr("class", "hit")
717
+ .attr("y", BAR_Y - 4)
718
+ .attr("height", BAR_H + 8);
719
+ r.append("rect")
720
+ .attr("class", "bar")
721
+ .attr("rx", 5)
722
+ .attr("y", BAR_Y)
723
+ .attr("height", BAR_H);
724
+ r.append("text")
725
+ .attr("class", "bar-label")
726
+ .attr("y", BAR_Y + BAR_H / 2 + 4);
587
727
  r.append("g").attr("class", "marks");
588
728
  const cb = r.append("g").attr("class", "code-btn src");
589
729
  cb.append("circle").attr("r", 9);
@@ -617,16 +757,30 @@
617
757
  const tw = shown.length * 6.6;
618
758
  // an outside label that would run off the chart hangs left of its bar instead
619
759
  const flip = outside && x0 + Math.max(w, 3) + 6 + tw > W;
620
- const lx = flip ? x0 - 6 : outside ? x0 + Math.max(w, 3) + 6 : Math.min(clipX + 8, x0 + w - 20);
621
- t.text(shown).classed("outside", outside).attr("x", lx).attr("text-anchor", flip ? "end" : "start");
760
+ const lx = flip
761
+ ? x0 - 6
762
+ : outside
763
+ ? x0 + Math.max(w, 3) + 6
764
+ : Math.min(clipX + 8, x0 + w - 20);
765
+ t.text(shown)
766
+ .classed("outside", outside)
767
+ .attr("x", lx)
768
+ .attr("text-anchor", flip ? "end" : "start");
622
769
  // the hit target covers bar + label, padded so a sliver of a bar is still easy to reach
623
770
  const left = Math.min(x0, flip ? lx - tw : lx) - 6;
624
771
  const right = Math.max(x0 + Math.max(w, 3), flip ? x0 : lx + tw) + 6;
625
- d3.select(this.parentNode).select("rect.hit").attr("x", left).attr("width", Math.max(16, right - left));
772
+ d3.select(this.parentNode)
773
+ .select("rect.hit")
774
+ .attr("x", left)
775
+ .attr("width", Math.max(16, right - left));
626
776
  // the <> sits just past the block (or just before it, when that would leave the chart)
627
777
  // …and, for a block spanning the chart, inside it right after its label
628
778
  const cx =
629
- right + 8 <= W - 10 ? right + 8 : left - 8 >= 10 ? left - 8 : Math.min(W - 12, (flip ? x0 : lx + tw) + 14);
779
+ right + 8 <= W - 10
780
+ ? right + 8
781
+ : left - 8 >= 10
782
+ ? left - 8
783
+ : Math.min(W - 12, (flip ? x0 : lx + tw) + 14);
630
784
  d3.select(this.parentNode)
631
785
  .select("g.code-btn.src")
632
786
  .style("display", hasPopup(n) ? null : "none")
@@ -647,7 +801,10 @@
647
801
  rowIndex = index;
648
802
  drawWires(x);
649
803
 
650
- chartSvg.select("line.pin").attr("x1", state.pin != null ? x(state.pin) : -10).attr("x2", state.pin != null ? x(state.pin) : -10);
804
+ chartSvg
805
+ .select("line.pin")
806
+ .attr("x1", state.pin != null ? x(state.pin) : -10)
807
+ .attr("x2", state.pin != null ? x(state.pin) : -10);
651
808
  if (app.classList.contains("hl-on") || state.errHl) applyHl();
652
809
  }
653
810
 
@@ -658,7 +815,12 @@
658
815
  function linkTip(l, count) {
659
816
  const from = esc(displayName(l.from));
660
817
  const to = esc(displayName(l.to));
661
- const title = l.op === "once" ? `once · ${esc(l.key)}` : l.op === "cancel" ? "cancel" : `ref · ${esc(l.key)}()`;
818
+ const title =
819
+ l.op === "once"
820
+ ? `once · ${esc(l.key)}`
821
+ : l.op === "cancel"
822
+ ? "cancel"
823
+ : `ref · ${esc(l.key)}()`;
662
824
  const what =
663
825
  l.op === "once"
664
826
  ? `<b>${from}</b> waited for <b>${esc(l.key)}</b> on <b>${to}</b>`
@@ -779,7 +941,8 @@
779
941
  }
780
942
  function worst(d) {
781
943
  const all = [d.m, ...d.more].map((m) => m.kind);
782
- for (const k of ["fail", "error", "cancel", "snapshot", "artifact", "pass", "event"]) if (all.includes(k)) return k;
944
+ for (const k of ["fail", "error", "cancel", "snapshot", "artifact", "pass", "event"])
945
+ if (all.includes(k)) return k;
783
946
  return all[0];
784
947
  }
785
948
 
@@ -789,14 +952,36 @@
789
952
  if (state.view === "flow") return;
790
953
  const w = el.chart.clientWidth || W;
791
954
  svg.attr("viewBox", null);
792
- const g = svg.selectAll("g.axis").data([0]).join("g").attr("class", "axis").attr("transform", "translate(0,33)");
793
- g.call(d3.axisTop(x).ticks(Math.max(2, Math.floor(w / 90))).tickFormat(fmtTick).tickSizeOuter(0));
955
+ const g = svg
956
+ .selectAll("g.axis")
957
+ .data([0])
958
+ .join("g")
959
+ .attr("class", "axis")
960
+ .attr("transform", "translate(0,33)");
961
+ g.call(
962
+ d3
963
+ .axisTop(x)
964
+ .ticks(Math.max(2, Math.floor(w / 90)))
965
+ .tickFormat(fmtTick)
966
+ .tickSizeOuter(0)
967
+ );
794
968
  g.selectAll(".tick text").attr("dy", "-0.5em");
795
- svg.selectAll("text.cursor-t").data([0]).join("text").attr("class", "cursor-t").attr("y", 12).attr("fill", css("--accent")).attr("font-size", 11);
969
+ svg
970
+ .selectAll("text.cursor-t")
971
+ .data([0])
972
+ .join("text")
973
+ .attr("class", "cursor-t")
974
+ .attr("y", 12)
975
+ .attr("fill", css("--accent"))
976
+ .attr("font-size", 11);
796
977
  }
797
978
  function moveCursor(px) {
798
979
  if (chartSvg) {
799
- chartSvg.select("line.cursor").attr("visibility", px == null ? "hidden" : "visible").attr("x1", px).attr("x2", px);
980
+ chartSvg
981
+ .select("line.cursor")
982
+ .attr("visibility", px == null ? "hidden" : "visible")
983
+ .attr("x1", px)
984
+ .attr("x2", px);
800
985
  }
801
986
  const label = d3.select(el.axis).select("text.cursor-t");
802
987
  if (px == null) label.text("");
@@ -878,13 +1063,25 @@
878
1063
  for (const l of model.links) {
879
1064
  const list = shotsBy.get(l.from) || shotsBy.set(l.from, []).get(l.from);
880
1065
  const prev = list[list.length - 1];
881
- if (l.op === "ref" && prev && prev.type === "link" && prev.l.op === "ref" && prev.l.to === l.to && prev.l.key === l.key) {
1066
+ if (
1067
+ l.op === "ref" &&
1068
+ prev &&
1069
+ prev.type === "link" &&
1070
+ prev.l.op === "ref" &&
1071
+ prev.l.to === l.to &&
1072
+ prev.l.key === l.key
1073
+ ) {
882
1074
  prev.n++;
883
1075
  continue;
884
1076
  }
885
- list.push({ type: "link", l, n: 1, t: l.t, name: l.label });
1077
+ // Placed by when the wait / call began, and never before its target started. By its end, a
1078
+ // wait ties with the next step's start (the target ends, the next step launches); by its
1079
+ // start alone, it ties with its target's. Clamping to the target, with ties sorting after
1080
+ // a card, puts it right under what it waited on.
1081
+ list.push({ type: "link", l, n: 1, t: l.t, at: Math.max(l.t0, l.to.start), name: l.label });
886
1082
  }
887
- const byTime = (a, b) => (a.t ?? Infinity) - (b.t ?? Infinity);
1083
+ const at = (x) => x.at ?? x.t ?? Infinity; // where a chip sits in its lane
1084
+ const byTime = (a, b) => at(a) - at(b);
888
1085
  const pos = new Map();
889
1086
  const lanes = [];
890
1087
  const chipRows = [];
@@ -906,7 +1103,7 @@
906
1103
  let si = 0;
907
1104
  for (const k of kids) {
908
1105
  const before = [];
909
- while (si < shots.length && (shots[si].t ?? Infinity) < k.start) before.push(shots[si++]);
1106
+ while (si < shots.length && at(shots[si]) < k.start) before.push(shots[si++]);
910
1107
  if (before.length) seq.push({ shots: before });
911
1108
  seq.push({ node: k });
912
1109
  }
@@ -1002,7 +1199,10 @@
1002
1199
  .attr("class", "flow-icon")
1003
1200
  .attr("transform", "translate(15,15) scale(0.58)")
1004
1201
  .attr("color", (n) => colorOf(n))
1005
- .html((n) => `<path d="${ICON[iconOf(n)]}" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>`);
1202
+ .html(
1203
+ (n) =>
1204
+ `<path d="${ICON[iconOf(n)]}" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>`
1205
+ );
1006
1206
  card.each(function (n) {
1007
1207
  const lines = wrapName(boxLabel(n), 31);
1008
1208
  const t = d3.select(this);
@@ -1021,7 +1221,10 @@
1021
1221
  .text(`${nodeKind(n)} · ${n.status} · ${fmtDur(n.duration)} · +${fmtTick(n.start)}`);
1022
1222
  const tags = n.v.tags || [];
1023
1223
  if (!tags.length) return;
1024
- const row = t.append("g").attr("class", "tchips").attr("transform", `translate(42,${lines.length > 1 ? 56 : 51})`);
1224
+ const row = t
1225
+ .append("g")
1226
+ .attr("class", "tchips")
1227
+ .attr("transform", `translate(42,${lines.length > 1 ? 56 : 51})`);
1025
1228
  const room = CW - 42 - 10;
1026
1229
  let x = 0;
1027
1230
  for (let i = 0; i < tags.length; i++) {
@@ -1100,7 +1303,11 @@
1100
1303
  select(x2.l.from.id, "links");
1101
1304
  })
1102
1305
  .on("mouseenter", (ev, x2) => {
1103
- showTip(ev, x2.type === "link" ? linkTip(x2.l, x2.n) : galleryTip(x2), x2.type === "link");
1306
+ showTip(
1307
+ ev,
1308
+ x2.type === "link" ? linkTip(x2.l, x2.n) : galleryTip(x2),
1309
+ x2.type === "link"
1310
+ );
1104
1311
  if (x2.type === "link") drawFlowWires(x2);
1105
1312
  })
1106
1313
  .on("mousemove", (ev) => moveTip(ev))
@@ -1109,7 +1316,8 @@
1109
1316
  if (x2.type === "link") drawFlowWires(null);
1110
1317
  });
1111
1318
  shown.forEach((x2, i) => {
1112
- if (x2.type === "link") linkChips.push({ x2, cx: x + i * (TW + 4) + TW / 2, cy: y + TH / 2 });
1319
+ if (x2.type === "link")
1320
+ linkChips.push({ x2, cx: x + i * (TW + 4) + TW / 2, cy: y + TH / 2 });
1113
1321
  });
1114
1322
  cell
1115
1323
  .append("rect")
@@ -1132,7 +1340,9 @@
1132
1340
  .attr("x", TW / 2)
1133
1341
  .attr("y", TH / 2 + 3.5)
1134
1342
  .attr("text-anchor", "middle")
1135
- .text((x2) => (x2.type === "link" ? linkGlyph(x2) : x2.type === "snapshot" ? "{ }" : "file"));
1343
+ .text((x2) =>
1344
+ x2.type === "link" ? linkGlyph(x2) : x2.type === "snapshot" ? "{ }" : "file"
1345
+ );
1136
1346
  let end = shown.length * (TW + 4);
1137
1347
  if (shots.length > shown.length) {
1138
1348
  const more = g
@@ -1158,7 +1368,9 @@
1158
1368
  .attr("class", "flow-shot-t")
1159
1369
  .attr("x", end + 2)
1160
1370
  .attr("y", TH / 2 + 4)
1161
- .text(`${fmtRel(t)} · ${shots.length > 1 ? chipCount(shots) : truncate(shots[0].name, 22)}`);
1371
+ .text(
1372
+ `${fmtRel(t)} · ${shots.length > 1 ? chipCount(shots) : truncate(shots[0].name, 22)}`
1373
+ );
1162
1374
  }
1163
1375
  };
1164
1376
  for (const r of chipRows) {
@@ -1187,7 +1399,9 @@
1187
1399
  const k = mode === "start" ? Math.max(0.62, Math.min(1, fitK)) : Math.min(1.2, fitK);
1188
1400
  const fits = (b.x1 - b.x0) * k <= w && (b.y1 - b.y0) * k <= h;
1189
1401
  const t = fits
1190
- ? d3.zoomIdentity.translate(w / 2 - (k * (b.x0 + b.x1)) / 2, h / 2 - (k * (b.y0 + b.y1)) / 2).scale(k)
1402
+ ? d3.zoomIdentity
1403
+ .translate(w / 2 - (k * (b.x0 + b.x1)) / 2, h / 2 - (k * (b.y0 + b.y1)) / 2)
1404
+ .scale(k)
1191
1405
  : d3.zoomIdentity.translate(-k * b.x0, -k * b.y0).scale(k);
1192
1406
  (instant ? svg : svg.transition().duration(250)).call(flowZoom.transform, t);
1193
1407
  }
@@ -1216,7 +1430,10 @@
1216
1430
  .attr("fill", (n) => colorOf(n));
1217
1431
  ovBrush = d3
1218
1432
  .brushX()
1219
- .extent([[0, 0], [w, h]])
1433
+ .extent([
1434
+ [0, 0],
1435
+ [w, h],
1436
+ ])
1220
1437
  .on("start", (ev) => {
1221
1438
  if (ev.sourceEvent) brushing = true;
1222
1439
  })
@@ -1243,12 +1460,16 @@
1243
1460
  if (tab) state.detailOpen.add(tab);
1244
1461
  state.details = true;
1245
1462
  el.main.classList.remove("no-details");
1246
- el.tree.querySelectorAll(".row").forEach((r) => r.classList.toggle("selected", r.dataset.id === id));
1463
+ el.tree
1464
+ .querySelectorAll(".row")
1465
+ .forEach((r) => r.classList.toggle("selected", r.dataset.id === id));
1247
1466
  if (chartSvg) {
1248
1467
  chartSvg.selectAll("rect.bar").classed("selected", (n) => n.id === id);
1249
1468
  chartSvg.selectAll("g.r").classed("sel", (n) => n.id === id);
1250
1469
  }
1251
- d3.select(el.flow).selectAll("g.flow-card").classed("selected", (n) => n.id === id);
1470
+ d3.select(el.flow)
1471
+ .selectAll("g.flow-card")
1472
+ .classed("selected", (n) => n.id === id);
1252
1473
  renderDetails();
1253
1474
  }
1254
1475
  function focusNode(n) {
@@ -1315,7 +1536,9 @@
1315
1536
  const p = flowPos.get(n.id);
1316
1537
  if (svg.empty() || !p) return;
1317
1538
  const k = Math.max(0.8, d3.zoomTransform(svg.node()).k);
1318
- const t = d3.zoomIdentity.translate(el.flow.clientWidth / 2 - k * p.x, el.flow.clientHeight / 2 - k * p.y).scale(k);
1539
+ const t = d3.zoomIdentity
1540
+ .translate(el.flow.clientWidth / 2 - k * p.x, el.flow.clientHeight / 2 - k * p.y)
1541
+ .scale(k);
1319
1542
  svg.transition().duration(300).call(flowZoom.transform, t);
1320
1543
  }
1321
1544
 
@@ -1338,12 +1561,16 @@
1338
1561
  ${n.v.tags.length ? `<div class="k">Tags</div><div class="v">${chipsHtml(n.v.tags, Infinity, 40)}</div>` : ""}
1339
1562
  ${n.v.mcp ? `<div class="k">MCP</div><div class="v"><span class="badge-inline">${esc(n.v.mcp.mode)}</span></div>` : ""}
1340
1563
  <div class="k">Id</div><div class="v mono-sm">${esc(n.id)}</div>
1341
- ${userSite(n) ? `<div class="k">${siteLabel(n)}</div><div class="v defined">
1564
+ ${
1565
+ userSite(n)
1566
+ ? `<div class="k">${siteLabel(n)}</div><div class="v defined">
1342
1567
  <button class="icon-btn" data-copy-source aria-label="Copy relative path">${svgIcon("copy")}</button>
1343
1568
  ${hasPopup(n) ? `<button class="icon-btn" data-show-code aria-label="Show source">${svgIcon("code")}</button>` : ""}
1344
1569
  <a class="icon-btn" href="${esc(editorUrl(userSite(n)))}" aria-label="Open in VS Code" data-tip-text="Open in VS Code">${svgIcon("external")}</a>
1345
1570
  <span class="defined-name" >${esc(basename(sourceOf(n)))}</span>
1346
- </div>` : ""}
1571
+ </div>`
1572
+ : ""
1573
+ }
1347
1574
  </div>
1348
1575
  <div class="acc">${detailSections(n)
1349
1576
  .map((sec) =>
@@ -1360,7 +1587,10 @@
1360
1587
  if (copySrc) {
1361
1588
  copySrc.addEventListener("click", () => copySource(n));
1362
1589
  copySrc.addEventListener("mouseenter", (ev) =>
1363
- showTip(ev, `<div class="mono">${esc(sourceOf(n))}</div><div class="hint">Click (or press <kbd>C</kbd>) to copy</div>`)
1590
+ showTip(
1591
+ ev,
1592
+ `<div class="mono">${esc(sourceOf(n))}</div><div class="hint">Click (or press <kbd>C</kbd>) to copy</div>`
1593
+ )
1364
1594
  );
1365
1595
  copySrc.addEventListener("mousemove", (ev) => moveTip(ev));
1366
1596
  copySrc.addEventListener("mouseleave", hideTip);
@@ -1421,9 +1651,16 @@
1421
1651
  empty: !hasArgs(n) && !params.length,
1422
1652
  body: () =>
1423
1653
  (params.length ? `<div class="params">${params.map(paramHtml).join("")}</div>` : "") +
1424
- (hasArgs(n) ? `<div class="code-box">${argsHtml(n)}</div>` : '<div class="muted">No arguments.</div>'),
1654
+ (hasArgs(n)
1655
+ ? `<div class="code-box">${argsHtml(n)}</div>`
1656
+ : '<div class="muted">No arguments.</div>'),
1657
+ },
1658
+ {
1659
+ key: "value",
1660
+ title: "Returned",
1661
+ empty: !hasValue(n),
1662
+ body: () => `<div class="code-box">${valueHtml(n)}</div>`,
1425
1663
  },
1426
- { key: "value", title: "Returned", empty: !hasValue(n), body: () => `<div class="code-box">${valueHtml(n)}</div>` },
1427
1664
  {
1428
1665
  key: "snapshots",
1429
1666
  title: "Snapshots & files",
@@ -1529,7 +1766,8 @@
1529
1766
 
1530
1767
  function thumbHtml(g) {
1531
1768
  const i = model.gallery.indexOf(g);
1532
- if (g.image) return `<div class="thumb" data-gallery="${i}" title="${esc(g.name)}"><img alt="${esc(g.name)}" data-asset="${esc(g.f.asset)}"></div>`;
1769
+ if (g.image)
1770
+ return `<div class="thumb" data-gallery="${i}" title="${esc(g.name)}"><img alt="${esc(g.name)}" data-asset="${esc(g.f.asset)}"></div>`;
1533
1771
  const label = g.type === "snapshot" ? "{ }" : g.f.mime.split("/")[1] || "file";
1534
1772
  return `<div class="thumb" data-gallery="${i}" title="${esc(g.name)}"><span class="json-ico">${esc(label)}</span></div>`;
1535
1773
  }
@@ -1538,15 +1776,17 @@
1538
1776
  root.querySelectorAll("img[data-asset]").forEach((img) => {
1539
1777
  if (!img.src) img.src = model.asset(img.dataset.asset);
1540
1778
  });
1541
- root.querySelectorAll("[data-gallery]").forEach((t) =>
1542
- t.addEventListener("click", () => openGallery(model.gallery[+t.dataset.gallery]))
1543
- );
1544
- root.querySelectorAll("[data-link]").forEach((t) =>
1545
- t.addEventListener("click", () => setPin(model.links[+t.dataset.link].t))
1546
- );
1547
- root.querySelectorAll("[data-item]").forEach((t) =>
1548
- t.addEventListener("click", () => openItem(model.items[+t.dataset.item]))
1549
- );
1779
+ root
1780
+ .querySelectorAll("[data-gallery]")
1781
+ .forEach((t) =>
1782
+ t.addEventListener("click", () => openGallery(model.gallery[+t.dataset.gallery]))
1783
+ );
1784
+ root
1785
+ .querySelectorAll("[data-link]")
1786
+ .forEach((t) => t.addEventListener("click", () => setPin(model.links[+t.dataset.link].t)));
1787
+ root
1788
+ .querySelectorAll("[data-item]")
1789
+ .forEach((t) => t.addEventListener("click", () => openItem(model.items[+t.dataset.item])));
1550
1790
  }
1551
1791
 
1552
1792
  function openItem(item) {
@@ -1561,7 +1801,6 @@
1561
1801
  redrawX();
1562
1802
  }
1563
1803
 
1564
-
1565
1804
  // ── errors: the tab, and highlighting them in the graphs ──
1566
1805
  const ERR_TYPES = [
1567
1806
  ["failure", "Failures", "x"],
@@ -1580,11 +1819,15 @@
1580
1819
  );
1581
1820
  }
1582
1821
  function errorsHtml() {
1583
- if (!model.errors.length) return '<div class="muted">No errors: nothing failed, no assertion failed, and nothing wrote error output.</div>';
1822
+ if (!model.errors.length)
1823
+ return '<div class="muted">No errors: nothing failed, no assertion failed, and nothing wrote error output.</div>';
1584
1824
  const count = (t) => model.errors.filter((e) => t === "all" || e.type === t).length;
1585
1825
  const seg = [["all", "All"], ...ERR_TYPES.map(([k, l]) => [k, l])]
1586
1826
  .filter(([k]) => k === "all" || count(k))
1587
- .map(([k, l]) => `<button class="seg ${state.errFilter === k ? "active" : ""}" data-err-filter="${k}">${l} <span class="muted">${count(k)}</span></button>`)
1827
+ .map(
1828
+ ([k, l]) =>
1829
+ `<button class="seg ${state.errFilter === k ? "active" : ""}" data-err-filter="${k}">${l} <span class="muted">${count(k)}</span></button>`
1830
+ )
1588
1831
  .join("");
1589
1832
  const shown = errorsShown();
1590
1833
  const sections = ERR_TYPES.map(([type, label, icon]) => {
@@ -1599,8 +1842,14 @@
1599
1842
  function errEntryHtml(e) {
1600
1843
  const open = state.errOpen.has(e.idx);
1601
1844
  const who = e.node ? displayName(e.node) : "run";
1602
- const extra = e.type === "output" && e.lines.length > 1 ? `<span class="err-n">${e.lines.length} lines</span>` : "";
1603
- const src = e.type === "output" && e.source ? `<span class="err-src">${esc(truncate(e.source, 40))}</span>` : "";
1845
+ const extra =
1846
+ e.type === "output" && e.lines.length > 1
1847
+ ? `<span class="err-n">${e.lines.length} lines</span>`
1848
+ : "";
1849
+ const src =
1850
+ e.type === "output" && e.source
1851
+ ? `<span class="err-src">${esc(truncate(e.source, 40))}</span>`
1852
+ : "";
1604
1853
  return `<div class="err-entry ${e.type}${open ? " open" : ""}" data-err="${e.idx}">
1605
1854
  <div class="err-row">
1606
1855
  <span class="twist ${open ? "open" : ""}">${svgIcon("chevron")}</span>
@@ -1642,9 +1891,13 @@
1642
1891
  b.addEventListener("mouseenter", (ev) => showTip(ev, "Show in graph"));
1643
1892
  b.addEventListener("mouseleave", hideTip);
1644
1893
  });
1645
- body.querySelectorAll("[data-err-copy]").forEach((b) =>
1646
- b.addEventListener("click", () => copy(model.errors[+b.dataset.errCopy].text, "Copied the error"))
1647
- );
1894
+ body
1895
+ .querySelectorAll("[data-err-copy]")
1896
+ .forEach((b) =>
1897
+ b.addEventListener("click", () =>
1898
+ copy(model.errors[+b.dataset.errCopy].text, "Copied the error")
1899
+ )
1900
+ );
1648
1901
  }
1649
1902
  function refreshErrors() {
1650
1903
  if (state.lowerTab !== "errors") return;
@@ -1683,7 +1936,9 @@
1683
1936
  const path = only ? pathOf(only) : model.errPath;
1684
1937
  app.classList.toggle("hl-on", !!set);
1685
1938
  const mark = (sel, idOf) =>
1686
- sel.classed("err", (d) => !!set && set.has(idOf(d))).classed("err-path", (d) => !!set && path.has(idOf(d)));
1939
+ sel
1940
+ .classed("err", (d) => !!set && set.has(idOf(d)))
1941
+ .classed("err-path", (d) => !!set && path.has(idOf(d)));
1687
1942
  if (chartSvg) mark(chartSvg.selectAll("g.r"), (n) => n.id);
1688
1943
  mark(d3.select(el.flow).selectAll("g.flow-card"), (n) => n.id);
1689
1944
  el.tree.querySelectorAll(".row").forEach((r) => {
@@ -1702,14 +1957,22 @@
1702
1957
  function renderLower() {
1703
1958
  const tabs = [
1704
1959
  ["snapshots", `Snapshots (${model.gallery.length})`],
1705
- ["errors", `Errors <span class="tab-count${seriousCount() ? " bad" : model.errors.length ? " warn" : ""}">${model.errors.length}</span>`],
1960
+ [
1961
+ "errors",
1962
+ `Errors <span class="tab-count${seriousCount() ? " bad" : model.errors.length ? " warn" : ""}">${model.errors.length}</span>`,
1963
+ ],
1706
1964
  ["logs", "Logs"],
1707
1965
  ["asserts", `Assertions (${model.asserts.length})`],
1708
1966
  ["events", "Timeline events"],
1709
1967
  ];
1710
1968
  el.lower.innerHTML = `<div class="tabs">${tabs
1711
- .map(([k, l]) => `<button class="tab ${state.lowerTab === k ? "active" : ""}" data-tab="${k}">${l}</button>`)
1712
- .join("")}<span class="spacer"></span><input class="filter" placeholder="Filter…" value="${esc(state.filter)}"></div>
1969
+ .map(
1970
+ ([k, l]) =>
1971
+ `<button class="tab ${state.lowerTab === k ? "active" : ""}" data-tab="${k}">${l}</button>`
1972
+ )
1973
+ .join(
1974
+ ""
1975
+ )}<span class="spacer"></span><input class="filter" placeholder="Filter…" value="${esc(state.filter)}"></div>
1713
1976
  <div class="body">${lowerBodyHtml()}</div>`;
1714
1977
  el.lower.querySelectorAll(".tab").forEach((b) =>
1715
1978
  b.addEventListener("click", () => {
@@ -1729,32 +1992,50 @@
1729
1992
  const body = el.lower.querySelector(".body");
1730
1993
  if (state.lowerTab === "errors") wireErrors(body);
1731
1994
  wireItems(body);
1732
- body.querySelectorAll("tr[data-row]").forEach((tr) =>
1733
- tr.addEventListener("click", () => openItem(model.items[+tr.dataset.row]))
1734
- );
1995
+ body
1996
+ .querySelectorAll("tr[data-row]")
1997
+ .forEach((tr) =>
1998
+ tr.addEventListener("click", () => openItem(model.items[+tr.dataset.row]))
1999
+ );
1735
2000
  body.querySelectorAll("[data-locate]").forEach((b) => {
1736
2001
  b.addEventListener("click", (ev) => {
1737
2002
  ev.stopPropagation();
1738
2003
  locate(model.gallery[+b.dataset.locate]);
1739
2004
  });
1740
- b.addEventListener("mouseenter", (ev) => showTip(ev, `Show in the ${state.view === "flow" ? "flow" : "graph"}`));
2005
+ b.addEventListener("mouseenter", (ev) =>
2006
+ showTip(ev, `Show in the ${state.view === "flow" ? "flow" : "graph"}`)
2007
+ );
1741
2008
  b.addEventListener("mouseleave", hideTip);
1742
2009
  });
1743
2010
  }
1744
2011
  function matches(...parts) {
1745
2012
  const q = state.filter.trim().toLowerCase();
1746
- return !q || parts.some((p) => String(p || "").toLowerCase().includes(q));
2013
+ return (
2014
+ !q ||
2015
+ parts.some((p) =>
2016
+ String(p || "")
2017
+ .toLowerCase()
2018
+ .includes(q)
2019
+ )
2020
+ );
1747
2021
  }
1748
2022
  function lowerBodyHtml() {
1749
2023
  const tab = state.lowerTab;
1750
2024
  if (tab === "errors") return errorsHtml();
1751
2025
  if (tab === "snapshots") {
1752
2026
  const g = model.gallery.filter((x) => matches(x.name, x.node && x.node.v.path));
1753
- if (!g.length) return '<div class="muted">No snapshots, artifacts, or images in this run.</div>';
2027
+ if (!g.length)
2028
+ return '<div class="muted">No snapshots, artifacts, or images in this run.</div>';
1754
2029
  return `<div class="gallery">${g
1755
2030
  .map((x) => {
1756
- const sub = x.type === "snapshot" ? `snapshot · ${x.node ? esc(displayName(x.node)) : ""}` : `${esc(x.f.mime)} · ${fmtSize(x.f.size)}${x.f.omitted ? " · not embedded" : ""}`;
1757
- const at = x.t != null || x.node ? `<button class="icon-btn locate" data-locate="${model.gallery.indexOf(x)}" aria-label="Show in graph">${svgIcon("locate")}</button>` : "";
2031
+ const sub =
2032
+ x.type === "snapshot"
2033
+ ? `snapshot · ${x.node ? esc(displayName(x.node)) : ""}`
2034
+ : `${esc(x.f.mime)} · ${fmtSize(x.f.size)}${x.f.omitted ? " · not embedded" : ""}`;
2035
+ const at =
2036
+ x.t != null || x.node
2037
+ ? `<button class="icon-btn locate" data-locate="${model.gallery.indexOf(x)}" aria-label="Show in graph">${svgIcon("locate")}</button>`
2038
+ : "";
1758
2039
  return `<div class="gal-item">${thumbHtml(x)}<div class="cap"><span class="t">${x.t == null ? "–" : fmtTick(x.t)}</span><span class="n" title="${esc(x.name)}">${esc(x.name)}</span>${at}</div><div class="sz">${sub}</div></div>`;
1759
2040
  })
1760
2041
  .join("")}</div>`;
@@ -1773,7 +2054,8 @@
1773
2054
  rows.push({ i, idx });
1774
2055
  if (rows.length >= 3000) break;
1775
2056
  }
1776
- if (!rows.length) return `<div class="muted">${tab === "asserts" ? "No assertions in this run." : "Nothing matches."}</div>`;
2057
+ if (!rows.length)
2058
+ return `<div class="muted">${tab === "asserts" ? "No assertions in this run." : "Nothing matches."}</div>`;
1777
2059
  return `<table class="list"><tbody>${rows
1778
2060
  .map(({ i, idx }) => {
1779
2061
  const mk = MARK[i.kind];
@@ -1782,7 +2064,9 @@
1782
2064
  : "";
1783
2065
  return `<tr data-row="${idx}" class="${i.kind === "error" || i.kind === "fail" ? "err" : ""}"><td class="t">${fmtTick(i.t)}</td><td>${ico}</td><td class="p" title="${esc(i.e.path)}">${esc(i.node ? displayName(i.node) : i.e.path)}</td><td class="m">${esc(i.e.message)}</td></tr>`;
1784
2066
  })
1785
- .join("")}</tbody></table>${rows.length >= 3000 ? '<div class="muted">First 3000 rows — narrow with the filter.</div>' : ""}`;
2067
+ .join(
2068
+ ""
2069
+ )}</tbody></table>${rows.length >= 3000 ? '<div class="muted">First 3000 rows — narrow with the filter.</div>' : ""}`;
1786
2070
  }
1787
2071
 
1788
2072
  // ── modal ──
@@ -1797,11 +2081,18 @@
1797
2081
  body = `<img alt="${esc(g.name)}" src="${src}">`;
1798
2082
  actions = `<a class="btn solo" download="${esc(basename(g.f.file))}" href="${src}">${svgIcon("download")}Download</a>`;
1799
2083
  } else if (g.type === "file") {
1800
- body = g.f.text != null ? `<pre class="code">${esc(g.f.text)}</pre>` : `<div class="muted">Not embedded (${esc(g.f.omitted || "binary")}). Open it from the run folder.</div>`;
2084
+ body =
2085
+ g.f.text != null
2086
+ ? `<pre class="code">${esc(g.f.text)}</pre>`
2087
+ : `<div class="muted">Not embedded (${esc(g.f.omitted || "binary")}). Open it from the run folder.</div>`;
1801
2088
  } else {
1802
2089
  body = `<pre class="code">${esc(g.s.value !== undefined ? json(g.s.value) : g.s.text)}</pre>${g.s.truncated ? '<div class="muted">Truncated — the snapshot file holds the rest.</div>' : ""}`;
1803
2090
  }
1804
- const sub = [g.t != null ? fmtRel(g.t) : null, g.node ? g.node.v.path : null, g.type === "file" ? fmtSize(g.f.size) : "snapshot"]
2091
+ const sub = [
2092
+ g.t != null ? fmtRel(g.t) : null,
2093
+ g.node ? g.node.v.path : null,
2094
+ g.type === "file" ? fmtSize(g.f.size) : "snapshot",
2095
+ ]
1805
2096
  .filter(Boolean)
1806
2097
  .join(" · ");
1807
2098
  const m = document.createElement("div");
@@ -1850,14 +2141,20 @@
1850
2141
  renderDetails();
1851
2142
  renderLower();
1852
2143
  }
1853
- if (a === "data") download(`${model.data.name}-run.json`, JSON.stringify(model.data, null, 2), "application/json");
2144
+ if (a === "data")
2145
+ download(
2146
+ `${model.data.name}-run.json`,
2147
+ JSON.stringify(model.data, null, 2),
2148
+ "application/json"
2149
+ );
1854
2150
  if (a === "folder") copy(model.data.folder);
1855
2151
  if (a === "expand") {
1856
2152
  state.collapsed.clear();
1857
2153
  renderAll();
1858
2154
  }
1859
2155
  if (a === "collapse") {
1860
- for (const n of model.nodes) if (n.depth >= 1 && n.children.length) state.collapsed.add(n.id);
2156
+ for (const n of model.nodes)
2157
+ if (n.depth >= 1 && n.children.length) state.collapsed.add(n.id);
1861
2158
  renderAll();
1862
2159
  }
1863
2160
  });
@@ -1892,7 +2189,9 @@
1892
2189
  n.depth === 0 ? "om" : v.kind === "step" ? "step" : "",
1893
2190
  v.mcp ? `<span class="mcp">MCP · ${esc(v.mcp.mode)}</span>` : "",
1894
2191
  ].filter(Boolean);
1895
- const fails = model.run.failures.filter((f) => f.action === n.id || f.action === n.name || f.action === v.path);
2192
+ const fails = model.run.failures.filter(
2193
+ (f) => f.action === n.id || f.action === n.name || f.action === v.path
2194
+ );
1896
2195
  return `<div class="tt">${esc(shown)}</div>
1897
2196
  ${call ? `<div class="tcall">${callHtml(n, call)}</div>` : ""}
1898
2197
  <div class="tmeta">${meta.join('<span class="sep">·</span>')}</div>
@@ -1902,9 +2201,10 @@
1902
2201
  }
1903
2202
  function galleryTip(x) {
1904
2203
  const img = x.image ? `<img src="${model.asset(x.f.asset)}" alt="">` : "";
1905
- const body = x.type === "snapshot"
1906
- ? `<div class="mono small">${esc(truncate(x.s.value !== undefined ? oneLine(x.s.value) : x.s.text || "", 200))}</div>`
1907
- : `<div class="ts">${esc(x.f.mime)} · ${fmtSize(x.f.size)}</div>`;
2204
+ const body =
2205
+ x.type === "snapshot"
2206
+ ? `<div class="mono small">${esc(truncate(x.s.value !== undefined ? oneLine(x.s.value) : x.s.text || "", 200))}</div>`
2207
+ : `<div class="ts">${esc(x.f.mime)} · ${fmtSize(x.f.size)}</div>`;
1908
2208
  return `<div class="tt">${esc(x.name)}${x.t != null ? ` · ${fmtRel(x.t)}` : ""}</div>${img}${body}`;
1909
2209
  }
1910
2210
  /** The om itself: its node tooltip, plus where the run's record lives. */
@@ -1914,12 +2214,23 @@
1914
2214
  function markTip(d) {
1915
2215
  const m = d.m;
1916
2216
  if (m.kind === "link") {
1917
- return linkTip(m.link) + (d.more.length ? `<div class="ts" style="margin-top:4px">+${d.more.length} more within a few ms</div>` : "");
2217
+ return (
2218
+ linkTip(m.link) +
2219
+ (d.more.length
2220
+ ? `<div class="ts" style="margin-top:4px">+${d.more.length} more within a few ms</div>`
2221
+ : "")
2222
+ );
1918
2223
  }
1919
2224
  const mk = MARK[m.kind] || MARK.event;
1920
- const img = m.gallery && m.gallery.image ? `<img src="${model.asset(m.gallery.f.asset)}" alt="">` : "";
1921
- const more = d.more.length ? `<div class="ts">+${d.more.length} more within a few ms</div>` : "";
1922
- const extra = m.gallery && m.gallery.image ? `<div class="ts">${esc(m.gallery.f.rel)} · ${fmtSize(m.gallery.f.size)}</div>` : "";
2225
+ const img =
2226
+ m.gallery && m.gallery.image ? `<img src="${model.asset(m.gallery.f.asset)}" alt="">` : "";
2227
+ const more = d.more.length
2228
+ ? `<div class="ts">+${d.more.length} more within a few ms</div>`
2229
+ : "";
2230
+ const extra =
2231
+ m.gallery && m.gallery.image
2232
+ ? `<div class="ts">${esc(m.gallery.f.rel)} · ${fmtSize(m.gallery.f.size)}</div>`
2233
+ : "";
1923
2234
  return `<div class="tt">${esc(mk.label)} · ${fmtRel(m.t)}</div>${img}<div>${esc(truncate(m.kind === "snapshot" || m.kind === "artifact" ? m.label : m.e.message, 260))}</div>${extra}${more}`;
1924
2235
  }
1925
2236
  // ── source popover (the <> buttons) — click-toggled, like the tskb explorer's ──
@@ -1964,7 +2275,9 @@
1964
2275
  const line = lines[at - c.start];
1965
2276
  if (line == null) return null;
1966
2277
  let call = null;
1967
- for (const m of line.matchAll(/(?:^|[^\w$.])([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\(/g)) {
2278
+ for (const m of line.matchAll(
2279
+ /(?:^|[^\w$.])([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\(/g
2280
+ )) {
1968
2281
  if (!/^(if|for|while|switch|return|catch|function|await|typeof|new|async)$/.test(m[1])) {
1969
2282
  call = { callee: m[1], index: m.index };
1970
2283
  break;
@@ -1977,7 +2290,9 @@
1977
2290
  const m =
1978
2291
  DECL.exec(prefix) ||
1979
2292
  /^\s*([\w$.]+)\s*=(?!=)/.exec(prefix) ||
1980
- (/^\s*(?:await\s+)?$/.test(prefix) && /=\s*(?:await\s*)?$/.test(prev) ? DECL.exec(prev) : null);
2293
+ (/^\s*(?:await\s+)?$/.test(prefix) && /=\s*(?:await\s*)?$/.test(prev)
2294
+ ? DECL.exec(prev)
2295
+ : null);
1981
2296
  return { callee: call.callee, bind: m ? m[1].replace(/\s+/g, " ") : "" };
1982
2297
  }
1983
2298
  /** The popover's tabs for `n`: what it is (prompt, source), then what went in and came out. */
@@ -1985,9 +2300,19 @@
1985
2300
  const tabs = [];
1986
2301
  if (promptOf(n)) tabs.push({ key: "prompt", label: "Prompt" });
1987
2302
  const mine = userSite(n);
1988
- if (codeOf(mine)) tabs.push({ key: "user", label: siteLabel(n) === "Called at" ? "Call site" : "Source", site: mine });
2303
+ if (codeOf(mine))
2304
+ tabs.push({
2305
+ key: "user",
2306
+ label: siteLabel(n) === "Called at" ? "Call site" : "Source",
2307
+ site: mine,
2308
+ });
1989
2309
  const def = n.v.definedAt;
1990
- if (def && def !== mine && codeOf(def)) tabs.push({ key: "def", label: isLib(def) ? "Definition (omkit)" : "Definition", site: def });
2310
+ if (def && def !== mine && codeOf(def))
2311
+ tabs.push({
2312
+ key: "def",
2313
+ label: isLib(def) ? "Definition (omkit)" : "Definition",
2314
+ site: def,
2315
+ });
1991
2316
  if (hasArgs(n)) tabs.push({ key: "args", label: "Args" });
1992
2317
  if (hasValue(n)) tabs.push({ key: "value", label: "Returned" });
1993
2318
  return tabs;
@@ -2015,7 +2340,8 @@
2015
2340
  function labelHtml(n) {
2016
2341
  const c = callOf(n);
2017
2342
  if (!c) return esc(displayName(n));
2018
- const name = c.callee === displayName(n) ? "" : `<span class="sep"> · </span>${esc(displayName(n))}`;
2343
+ const name =
2344
+ c.callee === displayName(n) ? "" : `<span class="sep"> · </span>${esc(displayName(n))}`;
2019
2345
  return `<span class="tcall">${callHtml(n, c)}</span>${name}`;
2020
2346
  }
2021
2347
  /** `page = chromePage(…)`, from {@link callExpr}. */
@@ -2026,7 +2352,9 @@
2026
2352
  return codeTabs(n).length > 0;
2027
2353
  }
2028
2354
  function popupTip(n) {
2029
- return codeTabs(n).map((t) => esc(t.label)).join(" · ");
2355
+ return codeTabs(n)
2356
+ .map((t) => esc(t.label))
2357
+ .join(" · ");
2030
2358
  }
2031
2359
  function hasArgs(n) {
2032
2360
  return !!(n.v.args && n.v.args.length);
@@ -2035,7 +2363,9 @@
2035
2363
  return n.v.args.length === 1 ? n.v.args[0] : n.v.args;
2036
2364
  }
2037
2365
  function relOf(site) {
2038
- return (model.data.sources && model.data.sources[site]) || String(site).replace(/:\d+(:\d+)?$/, "");
2366
+ return (
2367
+ (model.data.sources && model.data.sources[site]) || String(site).replace(/:\d+(:\d+)?$/, "")
2368
+ );
2039
2369
  }
2040
2370
  function hasValue(n) {
2041
2371
  return n.v.value !== undefined || !!n.v.valueNote;
@@ -2044,11 +2374,20 @@
2044
2374
  function jsonHtml(value, what) {
2045
2375
  const text = json(value);
2046
2376
  const lines = highlight(text);
2047
- const kind = Array.isArray(value) ? `array · ${value.length} items` : value === null ? "null" : typeof value === "object" ? `object · ${Object.keys(value).length} keys` : typeof value;
2377
+ const kind = Array.isArray(value)
2378
+ ? `array · ${value.length} items`
2379
+ : value === null
2380
+ ? "null"
2381
+ : typeof value === "object"
2382
+ ? `object · ${Object.keys(value).length} keys`
2383
+ : typeof value;
2048
2384
  return `<div class="cp-tools"><span class="muted">${esc(kind)} · ${lines.length} line${lines.length > 1 ? "s" : ""} · ${fmtSize(text.length)}</span>
2049
2385
  <button class="btn solo small" data-a="copy-json" data-what="${what}">${svgIcon("copy")}Copy JSON</button></div>
2050
2386
  <div class="cp-code">${lines
2051
- .map((html, i) => `<div class="cl"><span class="ln">${i + 1}</span><span class="lc">${html || " "}</span></div>`)
2387
+ .map(
2388
+ (html, i) =>
2389
+ `<div class="cl"><span class="ln">${i + 1}</span><span class="lc">${html || " "}</span></div>`
2390
+ )
2052
2391
  .join("")}</div>`;
2053
2392
  }
2054
2393
  function valueHtml(n) {
@@ -2058,34 +2397,42 @@
2058
2397
  return jsonHtml(n.v.value, "value");
2059
2398
  }
2060
2399
  function argsHtml(n) {
2061
- return hasArgs(n) ? jsonHtml(argsOf(n), "args") : '<div class="pp"><div class="muted">No arguments.</div></div>';
2400
+ return hasArgs(n)
2401
+ ? jsonHtml(argsOf(n), "args")
2402
+ : '<div class="pp"><div class="muted">No arguments.</div></div>';
2062
2403
  }
2063
2404
  function wireValue(root, n) {
2064
- root.querySelectorAll('[data-a="copy-json"]').forEach((b) =>
2065
- b.addEventListener("click", () =>
2066
- b.dataset.what === "args" ? copy(json(argsOf(n)), "Copied the args") : copy(json(n.v.value), "Copied the returned value")
2067
- )
2068
- );
2405
+ root
2406
+ .querySelectorAll('[data-a="copy-json"]')
2407
+ .forEach((b) =>
2408
+ b.addEventListener("click", () =>
2409
+ b.dataset.what === "args"
2410
+ ? copy(json(argsOf(n)), "Copied the args")
2411
+ : copy(json(n.v.value), "Copied the returned value")
2412
+ )
2413
+ );
2069
2414
  }
2070
2415
  function codeHtml(site) {
2071
2416
  const c = codeOf(site);
2072
2417
  const line = Number((/:(\d+)(?::\d+)?$/.exec(site) || [])[1]);
2073
2418
  const lines = highlight(c.text).map(
2074
- (html, i) => `<div class="cl${c.start + i === line ? " at" : ""}"><span class="ln">${c.start + i}</span><span class="lc">${html || " "}</span></div>`
2419
+ (html, i) =>
2420
+ `<div class="cl${c.start + i === line ? " at" : ""}"><span class="ln">${c.start + i}</span><span class="lc">${html || " "}</span></div>`
2075
2421
  );
2076
2422
  return `<div class="cp-code">${lines.join("")}</div>${c.truncated ? '<div class="muted cp-more">Cut short — open the file for the rest.</div>' : ""}`;
2077
2423
  }
2078
2424
  function promptHtml(p) {
2079
- const choices = p.choices && p.choices.length
2080
- ? `<div class="pp-k">Choices</div><div class="pp-v"><ol class="pp-choices">${p.choices
2081
- .map((c) => {
2082
- const value = typeof c === "string" ? c : c.value;
2083
- const label = typeof c === "string" ? c : c.label;
2084
- const picked = p.answer && p.answer.value === value;
2085
- return `<li class="${picked ? "picked" : ""}">${esc(label)}${label !== value ? ` <span class="muted">(${esc(value)})</span>` : ""}</li>`;
2086
- })
2087
- .join("")}</ol></div>`
2088
- : "";
2425
+ const choices =
2426
+ p.choices && p.choices.length
2427
+ ? `<div class="pp-k">Choices</div><div class="pp-v"><ol class="pp-choices">${p.choices
2428
+ .map((c) => {
2429
+ const value = typeof c === "string" ? c : c.value;
2430
+ const label = typeof c === "string" ? c : c.label;
2431
+ const picked = p.answer && p.answer.value === value;
2432
+ return `<li class="${picked ? "picked" : ""}">${esc(label)}${label !== value ? ` <span class="muted">(${esc(value)})</span>` : ""}</li>`;
2433
+ })
2434
+ .join("")}</ol></div>`
2435
+ : "";
2089
2436
  const answer = p.answer
2090
2437
  ? `<div class="pp-answer"><span class="pp-k">Answer</span><pre class="code">${esc(typeof p.answer.value === "string" ? p.answer.value : json(p.answer.value))}</pre>${
2091
2438
  p.answer.via ? `<span class="badge-inline">via ${esc(p.answer.via)}</span>` : ""
@@ -2108,7 +2455,10 @@
2108
2455
  if (codeFor === n.id && (!want || (toggle && codeTab === want))) return closeCode();
2109
2456
  const tabs = codeTabs(n);
2110
2457
  if (!tabs.length) return;
2111
- const tab = tabs.find((t) => t.key === want) || tabs.find((t) => t.key !== "value" && t.key !== "args") || tabs[0];
2458
+ const tab =
2459
+ tabs.find((t) => t.key === want) ||
2460
+ tabs.find((t) => t.key !== "value" && t.key !== "args") ||
2461
+ tabs[0];
2112
2462
  codeTab = tab.key;
2113
2463
  hideTip();
2114
2464
  const reopen = codeFor !== n.id;
@@ -2117,21 +2467,33 @@
2117
2467
  const rel = site ? relOf(site) : "";
2118
2468
  const line = site ? (/:(\d+)(?::\d+)?$/.exec(site) || [])[1] : "";
2119
2469
  codeEl.innerHTML = `<div class="cp-head">
2120
- ${site ? `<span class="cp-path" title="${esc(site)}">${esc(rel)}<span class="muted">${line ? ":" + line : ""}</span></span>
2470
+ ${
2471
+ site
2472
+ ? `<span class="cp-path" title="${esc(site)}">${esc(rel)}<span class="muted">${line ? ":" + line : ""}</span></span>
2121
2473
  <button class="icon-btn" data-a="copy" aria-label="Copy path">${svgIcon("copy")}</button>
2122
- <a class="icon-btn" href="${esc(editorUrl(site))}" aria-label="Open in VS Code">${svgIcon("external")}</a>` : ""}
2474
+ <a class="icon-btn" href="${esc(editorUrl(site))}" aria-label="Open in VS Code">${svgIcon("external")}</a>`
2475
+ : ""
2476
+ }
2123
2477
  <span class="cp-name">${esc(displayName(n))}</span>
2124
2478
  <button class="icon-btn" data-a="close" aria-label="Close">${svgIcon("close")}</button>
2125
2479
  </div>
2126
2480
  ${tabs.length > 1 ? `<div class="cp-tabs">${tabs.map((t) => `<button class="tab ${t === tab ? "active" : ""}" data-tab="${t.key}">${esc(t.label)}</button>`).join("")}</div>` : ""}
2127
2481
  <div class="cp-body">${
2128
- tab.key === "prompt" ? promptHtml(promptOf(n)) : tab.key === "value" ? valueHtml(n) : tab.key === "args" ? argsHtml(n) : codeHtml(site)
2482
+ tab.key === "prompt"
2483
+ ? promptHtml(promptOf(n))
2484
+ : tab.key === "value"
2485
+ ? valueHtml(n)
2486
+ : tab.key === "args"
2487
+ ? argsHtml(n)
2488
+ : codeHtml(site)
2129
2489
  }</div>`;
2130
2490
  wireValue(codeEl, n);
2131
2491
  const cp = codeEl.querySelector('[data-a="copy"]');
2132
2492
  if (cp) cp.addEventListener("click", () => copy(rel, `Copied ${rel}`));
2133
2493
  codeEl.querySelector('[data-a="close"]').addEventListener("click", closeCode);
2134
- codeEl.querySelectorAll(".cp-tabs .tab").forEach((b) => b.addEventListener("click", () => toggleCode(n, null, b.dataset.tab)));
2494
+ codeEl
2495
+ .querySelectorAll(".cp-tabs .tab")
2496
+ .forEach((b) => b.addEventListener("click", () => toggleCode(n, null, b.dataset.tab)));
2135
2497
  codeEl.hidden = false;
2136
2498
  if (reopen) {
2137
2499
  const x = ev && ev.clientX != null ? ev.clientX : window.innerWidth / 2;
@@ -2147,7 +2509,8 @@
2147
2509
  if (at) at.scrollIntoView({ block: "center" });
2148
2510
  if (codeAway) document.removeEventListener("mousedown", codeAway, true);
2149
2511
  codeAway = (e) => {
2150
- if (!codeEl.contains(e.target) && !e.target.closest(".code-btn, [data-show-code]")) closeCode();
2512
+ if (!codeEl.contains(e.target) && !e.target.closest(".code-btn, [data-show-code]"))
2513
+ closeCode();
2151
2514
  };
2152
2515
  setTimeout(() => codeAway && document.addEventListener("mousedown", codeAway, true), 0);
2153
2516
  }
@@ -2225,11 +2588,11 @@
2225
2588
  else if (e.key === "0") fit();
2226
2589
  else if ((e.key === "c" || e.key === "C") && !e.ctrlKey && !e.metaKey && !e.altKey) {
2227
2590
  copySource(hovered || model.byId.get(state.selected));
2228
- }
2229
- else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2591
+ } else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2230
2592
  const rows = visible();
2231
2593
  const i = rows.findIndex((n) => n.id === state.selected);
2232
- const next = rows[Math.max(0, Math.min(rows.length - 1, i + (e.key === "ArrowDown" ? 1 : -1)))];
2594
+ const next =
2595
+ rows[Math.max(0, Math.min(rows.length - 1, i + (e.key === "ArrowDown" ? 1 : -1)))];
2233
2596
  if (next) {
2234
2597
  e.preventDefault();
2235
2598
  select(next.id);
@@ -2259,7 +2622,9 @@
2259
2622
  const fails = parts.length
2260
2623
  ? `<span class="sep">•</span><button class="linkish ${serious ? "bad" : "warn"}" id="go-errors" title="Open the Errors tab and highlight them in the graphs">${parts.join(" · ")}</button>`
2261
2624
  : "";
2262
- const windows = [0, 100, 500, 1000, 5000, 10000, 30000, 60000, 300000].filter((w) => w === 0 || w < model.span);
2625
+ const windows = [0, 100, 500, 1000, 5000, 10000, 30000, 60000, 300000].filter(
2626
+ (w) => w === 0 || w < model.span
2627
+ );
2263
2628
  return `
2264
2629
  <header class="top">
2265
2630
  <div class="titles">
@@ -2328,10 +2693,10 @@
2328
2693
  : shape === "ring"
2329
2694
  ? `<span class="dot" style="width:11px;height:11px;background:var(--panel);box-shadow:inset 0 0 0 2px ${c}"></span>`
2330
2695
  : shape === "dash"
2331
- ? `<span style="width:16px;height:0;border-top:2px dashed ${c}"></span>`
2332
- : shape === "line"
2333
- ? `<span style="width:2px;height:14px;background:${c};margin:0 7px"></span>`
2334
- : `<span class="dot" style="background:${c};width:11px;height:11px"></span>`;
2696
+ ? `<span style="width:16px;height:0;border-top:2px dashed ${c}"></span>`
2697
+ : shape === "line"
2698
+ ? `<span style="width:2px;height:14px;background:${c};margin:0 7px"></span>`
2699
+ : `<span class="dot" style="background:${c};width:11px;height:11px"></span>`;
2335
2700
  return `<div class="li">${sw}${esc(label)}</div>`;
2336
2701
  }
2337
2702
 
@@ -2340,7 +2705,13 @@
2340
2705
  return n.branch < 0 ? css("--c1") : css(PALETTE[n.branch]);
2341
2706
  }
2342
2707
  function statusVar(s) {
2343
- return s === "ok" ? "--ok" : s === "failed" ? "--fail" : s === "cancelled" ? "--cancel" : "--running";
2708
+ return s === "ok"
2709
+ ? "--ok"
2710
+ : s === "failed"
2711
+ ? "--fail"
2712
+ : s === "cancelled"
2713
+ ? "--cancel"
2714
+ : "--running";
2344
2715
  }
2345
2716
  function alpha(color, a) {
2346
2717
  const c = d3.color(color);
@@ -2349,7 +2720,9 @@
2349
2720
  return c.formatRgb();
2350
2721
  }
2351
2722
  const KEYWORDS = new Set(
2352
- "abstract as async await break case catch class const continue default delete do else enum export extends false finally for from function get if implements import in instanceof interface keyof let new null of private protected public readonly return satisfies set static super switch this throw true try type typeof undefined var void while yield".split(" ")
2723
+ "abstract as async await break case catch class const continue default delete do else enum export extends false finally for from function get if implements import in instanceof interface keyof let new null of private protected public readonly return satisfies set static super switch this throw true try type typeof undefined var void while yield".split(
2724
+ " "
2725
+ )
2353
2726
  );
2354
2727
  const TOKEN =
2355
2728
  /(\/\/[^\n]*|\/\*[\s\S]*?\*\/)|(`(?:\\[\s\S]|[^`\\])*`|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')|(\b\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?n?\b)|([A-Za-z_$][\w$]*)/g;
@@ -2363,7 +2736,8 @@
2363
2736
  if (m.index > last) parts.push([null, text.slice(last, m.index)]);
2364
2737
  let cls = null;
2365
2738
  if (m[1]) cls = "tk-c";
2366
- else if (m[2]) cls = /^\s*:/.test(text.slice(TOKEN.lastIndex, TOKEN.lastIndex + 8)) ? "tk-p" : "tk-s";
2739
+ else if (m[2])
2740
+ cls = /^\s*:/.test(text.slice(TOKEN.lastIndex, TOKEN.lastIndex + 8)) ? "tk-p" : "tk-s";
2367
2741
  else if (m[3]) cls = "tk-n";
2368
2742
  else if (m[4]) {
2369
2743
  if (KEYWORDS.has(m[4])) cls = "tk-k";
@@ -2378,7 +2752,8 @@
2378
2752
  for (const [cls, chunk] of parts) {
2379
2753
  chunk.split("\n").forEach((piece, i) => {
2380
2754
  if (i > 0) lines.push("");
2381
- if (piece) lines[lines.length - 1] += cls ? `<span class="${cls}">${esc(piece)}</span>` : esc(piece);
2755
+ if (piece)
2756
+ lines[lines.length - 1] += cls ? `<span class="${cls}">${esc(piece)}</span>` : esc(piece);
2382
2757
  });
2383
2758
  }
2384
2759
  return lines;
@@ -2425,7 +2800,8 @@
2425
2800
  if (n._prompt !== undefined) return n._prompt;
2426
2801
  n._prompt = null;
2427
2802
  if (n.name !== "prompt" || n.depth === 0) return null;
2428
- const opts = (n.v.args && n.v.args[0] && typeof n.v.args[0] === "object" ? n.v.args[0] : {}) || {};
2803
+ const opts =
2804
+ (n.v.args && n.v.args[0] && typeof n.v.args[0] === "object" ? n.v.args[0] : {}) || {};
2429
2805
  let message = opts.message;
2430
2806
  let answer = null;
2431
2807
  for (const it of n.entries) {
@@ -2483,8 +2859,14 @@
2483
2859
  /** Up to `limit` tag chips, then a "+N" chip holding the rest. */
2484
2860
  function chipsHtml(tags, limit, max) {
2485
2861
  if (!tags || !tags.length) return "";
2486
- const more = tags.length > limit ? `<span class="chip more" title="${esc(tags.slice(limit).join(", "))}">+${tags.length - limit}</span>` : "";
2487
- return `<span class="chips">${tags.slice(0, limit).map((t) => chipHtml(t, max)).join("")}${more}</span>`;
2862
+ const more =
2863
+ tags.length > limit
2864
+ ? `<span class="chip more" title="${esc(tags.slice(limit).join(", "))}">+${tags.length - limit}</span>`
2865
+ : "";
2866
+ return `<span class="chips">${tags
2867
+ .slice(0, limit)
2868
+ .map((t) => chipHtml(t, max))
2869
+ .join("")}${more}</span>`;
2488
2870
  }
2489
2871
  function displayName(n) {
2490
2872
  if (n.depth === 0) return "main";
@@ -2498,7 +2880,8 @@
2498
2880
  function iconOf(n) {
2499
2881
  if (n.depth === 0) return "root";
2500
2882
  const s = `${n.name} ${(n.v.tags || []).join(" ")}`.toLowerCase();
2501
- if (/^(npm|npx|node|pnpm|yarn|tsc|git|bun)\b/.test(n.name) || /command|shell|exec/.test(s)) return "terminal";
2883
+ if (/^(npm|npx|node|pnpm|yarn|tsc|git|bun)\b/.test(n.name) || /command|shell|exec/.test(s))
2884
+ return "terminal";
2502
2885
  if (/chromepage|page:|tab/.test(s)) return "window";
2503
2886
  if (/browser|chrome/.test(s)) return "globe";
2504
2887
  if (/health|ready|gate|wait|port/.test(s)) return "pulse";
@@ -2511,7 +2894,8 @@
2511
2894
  }
2512
2895
  function copy(text, message) {
2513
2896
  const done = () => toast(message || "Copied");
2514
- if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(text).then(done, () => fallbackCopy(text, message));
2897
+ if (navigator.clipboard && window.isSecureContext)
2898
+ navigator.clipboard.writeText(text).then(done, () => fallbackCopy(text, message));
2515
2899
  else fallbackCopy(text, message);
2516
2900
  }
2517
2901
  function fallbackCopy(text, message) {
@@ -2543,7 +2927,9 @@
2543
2927
  }
2544
2928
  function toggleTheme() {
2545
2929
  const root = document.documentElement;
2546
- const dark = root.dataset.theme ? root.dataset.theme === "dark" : !matchMedia("(prefers-color-scheme: light)").matches;
2930
+ const dark = root.dataset.theme
2931
+ ? root.dataset.theme === "dark"
2932
+ : !matchMedia("(prefers-color-scheme: light)").matches;
2547
2933
  root.dataset.theme = dark ? "light" : "dark";
2548
2934
  try {
2549
2935
  localStorage.setItem("omkit-report-theme", root.dataset.theme);