@tangle-network/charts 0.21.13 → 0.21.14

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.
@@ -224,6 +224,9 @@
224
224
  .fw-root .fw-row[data-running] .fw-bar { transition: width 120ms linear; }
225
225
  /* The chain that gates the trace's end: an accent edge under its bars, so cutting latency there is visibly what moves the end. */
226
226
  .fw-root .fw-row[data-critical] .fw-bar { box-shadow: inset 0 -2px 0 var(--fw-accent); }
227
+ /* While searching, ancestors shown only for context recede and the matching names stand out. */
228
+ .fw-root .fw-row[data-search="context"] .fw-label { opacity: 0.55; }
229
+ .fw-root .fw-row[data-search="match"] .fw-name-text { font-weight: 600; }
227
230
  /* A restart: an amber notch through the bar at that moment; the bar keeps its own state. */
228
231
  .fw-root .fw-mark { position: absolute; z-index: 2; left: calc(var(--x0) - 1px); width: 2px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2 - 4px); height: calc(var(--fw-bar-h) + 8px);
229
232
  border-radius: 2px; background: var(--fw-amber); box-shadow: 0 0 0 1px var(--fw-surface); opacity: var(--on); pointer-events: auto; }
@@ -2590,15 +2590,19 @@ function segmentOf(row) {
2590
2590
  name: row.name
2591
2591
  };
2592
2592
  }
2593
+ /** The rows a search term matches by themselves, before ancestor context is added. */
2594
+ function searchMatchIds(rows, query, matchSpan) {
2595
+ const term = query.trim();
2596
+ const lower = term.toLowerCase();
2597
+ return new Set(rows.filter((row) => matchSpan ? matchSpan(row.span, term) : `${row.name} ${row.span.detail ?? ""} ${row.kind} ${row.ok ? "" : "failed"} ${row.span.status ?? ""}`.toLowerCase().includes(lower)).map((row) => row.id));
2598
+ }
2593
2599
  /** Search keeps ancestor context and deliberately reveals matches inside collapsed branches. */
2594
2600
  function visibleFlowWaterfallRows(rows, query, collapsed, matchSpan) {
2595
2601
  const byId = new Map(rows.map((row) => [row.id, row]));
2596
- const term = query.trim().toLowerCase();
2597
- if (term) {
2602
+ if (query.trim()) {
2598
2603
  const matching = /* @__PURE__ */ new Set();
2599
- for (const row of rows) {
2600
- if (matchSpan ? !matchSpan(row.span, query.trim()) : !`${row.name} ${row.span.detail ?? ""} ${row.kind} ${row.ok ? "" : "failed"} ${row.span.status ?? ""}`.toLowerCase().includes(term)) continue;
2601
- let cursor = row;
2604
+ for (const id of searchMatchIds(rows, query, matchSpan)) {
2605
+ let cursor = byId.get(id);
2602
2606
  while (cursor && !matching.has(cursor.id)) {
2603
2607
  matching.add(cursor.id);
2604
2608
  cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0;
@@ -2632,7 +2636,12 @@ function slowestRow(rows, clockMs) {
2632
2636
  * chain ends last wins. Cutting latency anywhere else leaves the trace's end unchanged. */
2633
2637
  function criticalPathIds(rows) {
2634
2638
  const children = /* @__PURE__ */ new Map();
2635
- for (const row of rows) if (row.parentId) children.set(row.parentId, [...children.get(row.parentId) ?? [], row]);
2639
+ for (const row of rows) {
2640
+ if (!row.parentId) continue;
2641
+ const siblings = children.get(row.parentId);
2642
+ if (siblings) siblings.push(row);
2643
+ else children.set(row.parentId, [row]);
2644
+ }
2636
2645
  let best;
2637
2646
  for (const root of rows) {
2638
2647
  if (root.parentId) continue;
@@ -3346,7 +3355,7 @@ function endLabel(row, clockMs) {
3346
3355
  }
3347
3356
  /** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
3348
3357
  const FlowRow = memo(function FlowRow(props) {
3349
- const { row, view, top, collapsed, searching, slowest, costliest, critical, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
3358
+ const { row, view, top, collapsed, searching, slowest, costliest, critical, matched, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
3350
3359
  const span = row.span;
3351
3360
  const running = spanRunning(span);
3352
3361
  const agentView = view === "agents";
@@ -3368,6 +3377,7 @@ const FlowRow = memo(function FlowRow(props) {
3368
3377
  "data-span-kind": kind,
3369
3378
  "data-slowest": slowest || void 0,
3370
3379
  "data-critical": critical || void 0,
3380
+ "data-search": searching ? matched ? "match" : "context" : void 0,
3371
3381
  "data-running": running || void 0,
3372
3382
  "aria-level": agentView ? void 0 : row.depth + 1,
3373
3383
  "aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
@@ -3562,7 +3572,7 @@ const FlowRow = memo(function FlowRow(props) {
3562
3572
  ] })
3563
3573
  })]
3564
3574
  });
3565
- }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3575
+ }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.matched === b.matched && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3566
3576
  /** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
3567
3577
  function LinkMark({ link, rowHeight, hot }) {
3568
3578
  const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
@@ -3774,6 +3784,12 @@ function FlowBody(props) {
3774
3784
  props.byId,
3775
3785
  view
3776
3786
  ]);
3787
+ const matched = useMemo(() => searching ? searchMatchIds(rows, props.query, props.matchSpan) : void 0, [
3788
+ searching,
3789
+ rows,
3790
+ props.query,
3791
+ props.matchSpan
3792
+ ]);
3777
3793
  const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
3778
3794
  const motion = useRowMotion(rows);
3779
3795
  const [jumped, setJumped] = useState();
@@ -3847,13 +3863,24 @@ function FlowBody(props) {
3847
3863
  }, []);
3848
3864
  const first = Math.max(0, chunk * CHUNK - OVERSCAN);
3849
3865
  const last = Math.min(rows.length, chunk * CHUNK + Math.ceil(viewport / rowHeight) + CHUNK + OVERSCAN);
3866
+ const scrollToRow = (i, element) => {
3867
+ const offset = (view === "spans" ? Math.min(STICKY_LEVELS, rows[i].depth) : 0) * rowHeight;
3868
+ if (i * rowHeight - offset < element.scrollTop) element.scrollTop = Math.max(0, i * rowHeight - offset);
3869
+ else if ((i + 1) * rowHeight > element.scrollTop + element.clientHeight) element.scrollTop = (i + 1) * rowHeight - element.clientHeight;
3870
+ };
3871
+ const shownSelection = useRef(void 0);
3872
+ useEffect(() => {
3873
+ if (!selectedId || selectedId === shownSelection.current) return;
3874
+ const i = index.get(selectedId);
3875
+ if (i === void 0 || !scroller.current) return;
3876
+ shownSelection.current = selectedId;
3877
+ scrollToRow(i, scroller.current);
3878
+ });
3850
3879
  const focusRow = (id) => {
3851
3880
  const i = index.get(id);
3852
3881
  const element = scroller.current;
3853
3882
  if (i === void 0 || !element) return;
3854
- const offset = (view === "spans" ? Math.min(STICKY_LEVELS, rows[i].depth) : 0) * rowHeight;
3855
- if (i * rowHeight - offset < element.scrollTop) element.scrollTop = Math.max(0, i * rowHeight - offset);
3856
- else if ((i + 1) * rowHeight > element.scrollTop + element.clientHeight) element.scrollTop = (i + 1) * rowHeight - element.clientHeight;
3883
+ scrollToRow(i, element);
3857
3884
  props.onActiveChange(id);
3858
3885
  requestAnimationFrame(() => element.querySelector(`.fw-canvas [data-span-id="${CSS.escape(id)}"]`)?.focus({ preventScroll: true }));
3859
3886
  };
@@ -4021,6 +4048,7 @@ function FlowBody(props) {
4021
4048
  slowest: item.id === slowestId,
4022
4049
  costliest: item.id === props.costliestId,
4023
4050
  critical: props.criticalIds?.has(item.id),
4051
+ matched: matched?.has(item.id),
4024
4052
  active: item.id === activeId,
4025
4053
  focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
4026
4054
  selected: item.id === selectedId,
@@ -4975,6 +5003,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4975
5003
  maxHeight: rowsHeight,
4976
5004
  collapsed,
4977
5005
  searching: Boolean(query.trim()),
5006
+ query,
5007
+ matchSpan,
4978
5008
  pauses: trace.pauses,
4979
5009
  cursorLine: position !== null || playing,
4980
5010
  sharedDetail,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.21.13",
3
+ "version": "0.21.14",
4
4
  "description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
5
5
  "type": "module",
6
6
  "license": "MIT",