thurview 0.17.1 → 0.18.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/dist/ui/app.js CHANGED
@@ -980,13 +980,48 @@
980
980
  if (a) openAnchorPeek(a);
981
981
  }
982
982
  function sequenceDiagram(b) {
983
- const colW = 170;
984
- const top = 44;
985
- const rowH = 34;
986
- const n = b.actors.length;
987
- const width = Math.max(colW * n, 320);
988
- const height = top + rowH * (b.messages.length + 1) + 10;
983
+ const charW = 12 * 0.605;
984
+ const lineH = 15;
985
+ const wrap = (text, room) => {
986
+ const max = Math.max(8, Math.floor(room / charW));
987
+ const lines = [];
988
+ for (const word of text.split(" ")) {
989
+ const last = lines.length - 1;
990
+ if (last >= 0 && lines[last].length + 1 + word.length <= max) lines[last] += ` ${word}`;
991
+ else lines.push(word);
992
+ }
993
+ return lines;
994
+ };
995
+ const widest = (lines) => Math.max(...lines.map((l) => l.length)) * charW;
996
+ const names = b.actors.map((a) => wrap(a.label, 20 * charW));
997
+ const nameLines = Math.max(...names.map((l) => l.length));
998
+ const boxH2 = 13 + nameLines * lineH;
999
+ const top = 6 + boxH2 + 10;
1000
+ const boxes = names.map((l) => Math.max(120, widest(l) + 20));
1001
+ const cols = boxes.map((w) => Math.max(150, w + 16));
1002
+ const centres = cols.map((w, i) => cols.slice(0, i).reduce((s, c) => s + c, 0) + w / 2);
1003
+ const x = (id) => centres[b.actors.findIndex((a) => a.id === id)];
1004
+ let y = top;
1005
+ const rows = b.messages.map((m2, i) => {
1006
+ const x1 = x(m2.from);
1007
+ const x2 = x(m2.to);
1008
+ const self = x1 === x2;
1009
+ const start = self ? x1 + 36 : Math.min(x1, x2) + 8;
1010
+ const lines = wrap(`${i + 1}. ${m2.label}`, Math.max(180, Math.abs(x2 - x1) - 16));
1011
+ y += 20 + lines.length * lineH;
1012
+ return { x1, x2, self, start, lines, y, end: start + widest(lines) };
1013
+ });
1014
+ const width = Math.ceil(
1015
+ Math.max(
1016
+ 320,
1017
+ cols.reduce((s, c) => s + c, 0),
1018
+ ...rows.map((r) => r.end + 12)
1019
+ )
1020
+ );
1021
+ const height = y + 24;
989
1022
  const el = svg("svg", { viewBox: `0 0 ${width} ${height}`, class: "seq" });
1023
+ el.style.maxWidth = `${width}px`;
1024
+ el.style.minWidth = `${Math.ceil(width * 0.9)}px`;
990
1025
  el.appendChild(
991
1026
  svg(
992
1027
  "defs",
@@ -1006,39 +1041,35 @@
1006
1041
  )
1007
1042
  )
1008
1043
  );
1009
- const x = (id) => b.actors.findIndex((a) => a.id === id) * colW + colW / 2;
1010
- b.actors.forEach((a, i) => {
1011
- const cx = i * colW + colW / 2;
1044
+ const tspans = (at, baseline, text) => text.map((l, i) => svg("tspan", { x: at, y: baseline - (text.length - 1 - i) * lineH }, l));
1045
+ b.actors.forEach((_, i) => {
1046
+ const cx = centres[i];
1047
+ const w = boxes[i];
1048
+ const name = names[i];
1012
1049
  const g = svg("g", { class: "actor" });
1013
- g.appendChild(svg("rect", { x: cx - 70, y: 6, width: 140, height: 28, rx: 6 }));
1014
- g.appendChild(svg("text", { x: cx, y: 25, "text-anchor": "middle" }, a.label));
1015
- g.appendChild(svg("line", { class: "life", x1: cx, y1: 34, x2: cx, y2: height - 6 }));
1050
+ g.appendChild(svg("rect", { x: cx - w / 2, y: 6, width: w, height: boxH2, rx: 6 }));
1051
+ const baseline = 6 + (boxH2 + name.length * lineH) / 2 - 4;
1052
+ g.appendChild(svg("text", { "text-anchor": "middle" }, ...tspans(cx, baseline, name)));
1053
+ g.appendChild(svg("line", { class: "life", x1: cx, y1: 6 + boxH2, x2: cx, y2: height - 6 }));
1016
1054
  el.appendChild(g);
1017
1055
  });
1018
1056
  b.messages.forEach((m2, i) => {
1019
- const y = top + rowH * (i + 1);
1020
- const x1 = x(m2.from);
1021
- const x2 = x(m2.to);
1057
+ const { x1, x2, self, start, y: y2 } = rows[i];
1022
1058
  const g = svg("g", { class: "msg" });
1023
- const self = x1 === x2;
1024
1059
  if (self) {
1025
1060
  g.appendChild(
1026
1061
  svg("path", {
1027
- d: `M${x1},${y - 10} h30 v20 h-30`,
1062
+ d: `M${x1},${y2 - 10} h30 v20 h-30`,
1028
1063
  fill: "none",
1029
1064
  stroke: "currentColor",
1030
1065
  "marker-end": "url(#arr)"
1031
1066
  })
1032
1067
  );
1033
1068
  } else {
1034
- g.appendChild(svg("line", { x1, y1: y, x2: x2 + (x2 > x1 ? -4 : 4), y2: y }));
1069
+ g.appendChild(svg("line", { x1, y1: y2, x2: x2 + (x2 > x1 ? -4 : 4), y2 }));
1035
1070
  }
1036
- const label2 = svg(
1037
- "text",
1038
- { x: self ? x1 + 36 : (x1 + x2) / 2, y: y - 6, "text-anchor": self ? "start" : "middle" },
1039
- `${i + 1}. ${m2.label}`
1040
- );
1041
- label2.addEventListener("click", (e) => {
1071
+ const label = svg("text", {}, ...tspans(start, y2 - 6, rows[i].lines));
1072
+ const open = (at) => {
1042
1073
  if (m2.anchor) openAnchor(m2.anchor);
1043
1074
  else if (m2.code)
1044
1075
  popover(
@@ -1047,13 +1078,28 @@
1047
1078
  { class: "def-popover" },
1048
1079
  h("pre", { style: { margin: "0", border: "none" } }, m2.code.text)
1049
1080
  ),
1050
- { x: e.pageX, y: e.pageY + 8 }
1081
+ at
1051
1082
  );
1083
+ };
1084
+ label.addEventListener("click", (e) => open({ x: e.pageX, y: e.pageY + 8 }));
1085
+ label.setAttribute("role", "button");
1086
+ label.setAttribute("tabindex", "0");
1087
+ label.addEventListener("keydown", (e) => {
1088
+ const k = e.key;
1089
+ if (k !== "Enter" && k !== " ") return;
1090
+ e.preventDefault();
1091
+ const r = label.getBoundingClientRect();
1092
+ open({ x: r.left + window.scrollX, y: r.bottom + window.scrollY + 8 });
1052
1093
  });
1053
- g.appendChild(label2);
1094
+ g.appendChild(label);
1054
1095
  el.appendChild(g);
1055
1096
  });
1056
- return h("div", { class: "diagram" }, h("div", { class: "dhead" }, b.label), el);
1097
+ return h(
1098
+ "div",
1099
+ { class: "diagram" },
1100
+ h("div", { class: "dhead" }, b.label),
1101
+ h("div", { class: "seq-scroll" }, el)
1102
+ );
1057
1103
  }
1058
1104
  function callstackDiff(b, doc) {
1059
1105
  const rows = b.rows.map((r, i) => {
@@ -1209,11 +1255,11 @@
1209
1255
  )
1210
1256
  )
1211
1257
  );
1212
- for (const { edge, d, back, label: label2 } of edges) {
1258
+ for (const { edge, d, back, label } of edges) {
1213
1259
  const g = svg("g", { class: back ? "edge back" : "edge" });
1214
1260
  g.appendChild(svg("path", { d, fill: "none", "marker-end": "url(#flow-arr)" }));
1215
1261
  if (edge.case)
1216
- g.appendChild(svg("text", { x: label2.x, y: label2.y, "text-anchor": label2.at }, edge.case));
1262
+ g.appendChild(svg("text", { x: label.x, y: label.y, "text-anchor": label.at }, edge.case));
1217
1263
  el.appendChild(g);
1218
1264
  }
1219
1265
  for (const s of b.steps) {
@@ -1408,7 +1454,7 @@
1408
1454
  h(
1409
1455
  "p",
1410
1456
  { class: "ifd-verdict" },
1411
- delta ? delta.verdict : design ? "Unavailable: this revision declared no proposal." : "Unavailable: the code graph could not be built for these commits."
1457
+ delta ? delta.verdict : design ? "Unavailable: this revision declared no proposal." : "Unavailable: this revision recorded no interface delta."
1412
1458
  )
1413
1459
  );
1414
1460
  const entries = delta?.entries ?? [];
@@ -1913,7 +1959,7 @@
1913
1959
  }
1914
1960
  function gapRow(d, from, to, colspan, onExpand) {
1915
1961
  if (to < from) return null;
1916
- const count = to - from + 1;
1962
+ const count2 = to - from + 1;
1917
1963
  return h(
1918
1964
  "tr",
1919
1965
  { class: "gap" },
@@ -1933,7 +1979,7 @@
1933
1979
  );
1934
1980
  }
1935
1981
  },
1936
- `\u22EF ${count} unchanged line${count > 1 ? "s" : ""}`
1982
+ `\u22EF ${count2} unchanged line${count2 > 1 ? "s" : ""}`
1937
1983
  )
1938
1984
  );
1939
1985
  }
@@ -2055,9 +2101,9 @@
2055
2101
  let prevEnd = 0;
2056
2102
  const gapBoth = (from, to) => {
2057
2103
  if (to < from) return;
2058
- const count = to - from + 1;
2059
- const lg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count} unchanged`));
2060
- const rg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count} unchanged`));
2104
+ const count2 = to - from + 1;
2105
+ const lg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count2} unchanged`));
2106
+ const rg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count2} unchanged`));
2061
2107
  const expand = async () => {
2062
2108
  const f = await api.file(state.id, d.path, "head", from, to);
2063
2109
  const off = gapOffset(d, from) ?? 0;
@@ -2430,7 +2476,7 @@
2430
2476
  group(design ? "Context \u2014 untouched by the design" : "Context \u2014 unchanged here", cold)
2431
2477
  );
2432
2478
  };
2433
- const links2 = () => {
2479
+ const links = () => {
2434
2480
  const here = new Set(childrenOf(all, level, map.filesByNode).map((k) => k.id));
2435
2481
  const top = (id) => {
2436
2482
  let x = id;
@@ -2478,7 +2524,7 @@
2478
2524
  };
2479
2525
  const draw = () => {
2480
2526
  canvas.innerHTML = "";
2481
- append(canvas, [intro2(), crumbs(), board(), links2()]);
2527
+ append(canvas, [intro2(), crumbs(), board(), links()]);
2482
2528
  drawSide();
2483
2529
  };
2484
2530
  const drawSide = () => {
@@ -2598,6 +2644,12 @@
2598
2644
  // src/ui/views/coverage.ts
2599
2645
  init_dom();
2600
2646
  var SHOWN = 8;
2647
+ var LABELS = [
2648
+ ["explained", "anchored in the document"],
2649
+ ["placed", "placed on the map only"],
2650
+ ["searched", "matched by a recorded search only"],
2651
+ ["uncovered", "not examined"]
2652
+ ];
2601
2653
  function renderCoverage(root) {
2602
2654
  const cov = state.data?.coverage;
2603
2655
  if (!cov) {
@@ -2611,29 +2663,23 @@
2611
2663
  ]);
2612
2664
  return;
2613
2665
  }
2614
- append(root, [intro(cov), clusters(cov), links(cov), sharedNames(cov), unread(cov)]);
2666
+ append(root, [intro(cov), searches(cov), clusters(cov), owners(cov)]);
2615
2667
  }
2668
+ var count = (cov, s) => cov.states[s] ?? 0;
2616
2669
  function bar(cov) {
2617
2670
  const total = Math.max(cov.files.total, 1);
2618
- const seg = (n, cls, label2) => n ? h("span", {
2671
+ const seg = (n, cls, label) => n ? h("span", {
2619
2672
  class: `cov-seg ${cls}`,
2620
2673
  style: { width: `${n / total * 100}%` },
2621
- title: `${n} ${label2}`
2674
+ title: `${n} ${label}`
2622
2675
  }) : null;
2623
2676
  return h(
2624
2677
  "div",
2625
2678
  { class: "cov-bar" },
2626
- seg(cov.states.explained, "explained", "anchored in the document"),
2627
- seg(cov.states.placed, "placed", "placed on the map only"),
2628
- seg(cov.states.uncovered, "uncovered", "not examined")
2679
+ LABELS.map(([s, label]) => seg(count(cov, s), s, label))
2629
2680
  );
2630
2681
  }
2631
2682
  function intro(cov) {
2632
- const key = [
2633
- [cov.states.explained, "explained", "anchored in the document"],
2634
- [cov.states.placed, "placed", "placed on the map only"],
2635
- [cov.states.uncovered, "uncovered", "not examined"]
2636
- ];
2637
2683
  return h(
2638
2684
  "div",
2639
2685
  { class: "cov-intro" },
@@ -2643,14 +2689,19 @@
2643
2689
  null,
2644
2690
  "A codebase does not fit in a short document, so this one selects. Here is the",
2645
2691
  " selection, counted rather than claimed: every file in scope at the pinned commit,",
2646
- " and which of the three states it is in."
2692
+ " and which of the four states it is in."
2647
2693
  ),
2648
2694
  bar(cov),
2649
2695
  h(
2650
2696
  "div",
2651
2697
  { class: "cov-key" },
2652
- key.map(
2653
- ([n, cls, label2]) => h("span", { class: "cov-key-item" }, h("i", { class: `cov-dot ${cls}` }), `${n} ${label2}`)
2698
+ LABELS.map(
2699
+ ([s, label]) => h(
2700
+ "span",
2701
+ { class: "cov-key-item" },
2702
+ h("i", { class: `cov-dot ${s}` }),
2703
+ `${count(cov, s)} ${label}`
2704
+ )
2654
2705
  )
2655
2706
  ),
2656
2707
  h(
@@ -2658,15 +2709,12 @@
2658
2709
  { class: "cov-facts" },
2659
2710
  fact("scope", cov.scope === "**" ? "the whole repository" : cov.scope),
2660
2711
  fact("commit", cov.commit.slice(0, 12)),
2661
- fact("files in scope", String(cov.files.total)),
2662
- fact("read by the code graph", `${cov.files.inGraph} of ${cov.files.total}`),
2663
- cov.unresolved ? fact("references the graph could not place", String(cov.unresolved)) : null,
2664
- cov.truncated ? fact("file list capped", "the graph is partial; treat every count as a floor") : null
2712
+ fact("files in scope", String(cov.files.total))
2665
2713
  )
2666
2714
  );
2667
2715
  }
2668
- function fact(label2, value) {
2669
- return h("div", { class: "cov-fact" }, h("b", null, value), h("span", { class: "muted" }, label2));
2716
+ function fact(label, value) {
2717
+ return h("div", { class: "cov-fact" }, h("b", null, value), h("span", { class: "muted" }, label));
2670
2718
  }
2671
2719
  function fileList(files, cls) {
2672
2720
  if (!files.length) return null;
@@ -2687,9 +2735,49 @@
2687
2735
  }
2688
2736
  return wrap;
2689
2737
  }
2738
+ function command(cov, s) {
2739
+ const quote = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
2740
+ const paths = s.paths.map((p) => ` ${quote(p)}`).join("");
2741
+ return `git grep -I -n -E -e ${quote(s.pattern)} ${cov.commit.slice(0, 12)} --${paths}`;
2742
+ }
2743
+ function searches(cov) {
2744
+ const list = cov.searches ?? [];
2745
+ if (!list.length) return null;
2746
+ return h(
2747
+ "div",
2748
+ { class: "cov-section" },
2749
+ h("h3", null, "What the agent searched"),
2750
+ h(
2751
+ "p",
2752
+ { class: "muted" },
2753
+ "Each search the agent recorded, re-run at the pinned commit. A search that matched",
2754
+ " nothing is listed too: that zero is what a claim like \u201Cnothing else calls it\u201D rests on."
2755
+ ),
2756
+ h(
2757
+ "div",
2758
+ { class: "cov-links" },
2759
+ list.map(
2760
+ (s) => h(
2761
+ "div",
2762
+ { class: "cov-link" },
2763
+ h("code", null, s.key),
2764
+ s.why ? h("span", null, s.why) : null,
2765
+ h(
2766
+ "span",
2767
+ { class: "muted mono" },
2768
+ `${s.hits} line${s.hits === 1 ? "" : "s"} in ${s.files.length} file${s.files.length === 1 ? "" : "s"}`
2769
+ ),
2770
+ h("code", { class: "muted" }, command(cov, s)),
2771
+ fileList(s.files, "searched")
2772
+ )
2773
+ )
2774
+ )
2775
+ );
2776
+ }
2690
2777
  function clusterRow(c) {
2691
2778
  const anchors = state.data?.document?.anchors ?? {};
2692
2779
  const firstAnchor = c.explained.length ? Object.values(anchors).find((a) => a.peek && c.explained.includes(a.peek.file)) : void 0;
2780
+ const badge = (files, cls, label) => files?.length ? h("span", { class: `badge cov-b-${cls}` }, `${files.length} ${label}`) : null;
2693
2781
  return h(
2694
2782
  "div",
2695
2783
  { class: "cov-cluster" },
@@ -2697,19 +2785,14 @@
2697
2785
  "div",
2698
2786
  { class: "cov-cluster-head" },
2699
2787
  h("code", { class: "cov-label" }, c.label),
2700
- h("span", { class: "muted" }, `${c.files} files \xB7 ${c.symbols} symbols`),
2788
+ h("span", { class: "muted" }, `${c.files} files`),
2701
2789
  h("span", { class: "spacer" }),
2702
- c.explained.length ? h("span", { class: "badge cov-b-explained" }, `${c.explained.length} anchored`) : null,
2703
- c.placed.length ? h("span", { class: "badge cov-b-placed" }, `${c.placed.length} placed`) : null,
2704
- c.uncovered.length ? h("span", { class: "badge cov-b-uncovered" }, `${c.uncovered.length} not examined`) : null,
2790
+ badge(c.explained, "explained", "anchored"),
2791
+ badge(c.placed, "placed", "placed"),
2792
+ badge(c.searched, "searched", "searched"),
2793
+ badge(c.uncovered, "uncovered", "not examined"),
2705
2794
  firstAnchor ? h("button", { class: "small", onclick: () => openAnchorPeek(firstAnchor) }, "Peek code \u25B8") : null
2706
2795
  ),
2707
- c.hubs.length ? h(
2708
- "div",
2709
- { class: "muted cov-hubs" },
2710
- "most referenced here: ",
2711
- c.hubs.map((n) => h("code", null, n))
2712
- ) : null,
2713
2796
  fileList(c.uncovered, "uncovered")
2714
2797
  );
2715
2798
  }
@@ -2717,153 +2800,47 @@
2717
2800
  return h(
2718
2801
  "div",
2719
2802
  { class: "cov-section" },
2720
- h("h3", null, "By part of the system"),
2803
+ h("h3", null, "By directory"),
2721
2804
  h(
2722
2805
  "p",
2723
2806
  { class: "muted" },
2724
- "Files clustered by how they reference each other at the pinned commit, largest first.",
2725
- " The files listed under a part are the ones this explainer never examined."
2807
+ "The files in scope by the directory they sit in, largest first.",
2808
+ " The files listed under a directory are the ones nothing in this explainer reached."
2726
2809
  ),
2727
2810
  cov.clusters.map(clusterRow)
2728
2811
  );
2729
2812
  }
2730
- function links(cov) {
2731
- if (!cov.links.length) return null;
2813
+ function owners(cov) {
2814
+ if (!cov.owners.length) return null;
2732
2815
  return h(
2733
2816
  "div",
2734
2817
  { class: "cov-section" },
2735
- h("h3", null, "What reaches what"),
2818
+ h("h3", null, "What each map node owns"),
2736
2819
  h(
2737
2820
  "p",
2738
2821
  { class: "muted" },
2739
- "References that cross from one part to another, with how many there are.",
2740
- " A pair marked both ways references each other in both directions."
2822
+ "A file counts as placed because a map node's globs match it. The globs are here",
2823
+ " so a broad one is visible rather than silently inflating the count."
2741
2824
  ),
2742
2825
  h(
2743
2826
  "div",
2744
2827
  { class: "cov-links" },
2745
- cov.links.slice(0, 24).map(
2746
- (l) => h(
2828
+ cov.owners.map(
2829
+ (o) => h(
2747
2830
  "div",
2748
- { class: "cov-link" },
2749
- h("code", null, label(cov, l.from)),
2750
- h("span", { class: "muted" }, "\u2192"),
2751
- h("code", null, label(cov, l.to)),
2752
- h("span", { class: "muted mono" }, `${l.references} refs`),
2753
- l.bothWays ? h("span", { class: "badge" }, "both ways") : null
2754
- )
2755
- )
2756
- )
2757
- );
2758
- }
2759
- function label(cov, id) {
2760
- return cov.clusters.find((c) => c.id === id)?.label ?? id;
2761
- }
2762
- function sharedNames(cov) {
2763
- if (!cov.sharedNames.length) return null;
2764
- return h(
2765
- "div",
2766
- { class: "cov-section" },
2767
- h("h3", null, "Names defined in more than one part"),
2768
- h(
2769
- "p",
2770
- { class: "muted" },
2771
- `${cov.sharedNamesTotal} name${cov.sharedNamesTotal === 1 ? " at this commit is" : "s at this commit are"}`,
2772
- " defined in two or more parts. What that means here - the same idea in two places,",
2773
- " two different ideas sharing a word, or a name too common to mean anything - is",
2774
- " what the code says and this page does not."
2775
- ),
2776
- h(
2777
- "div",
2778
- { class: "cov-shared" },
2779
- cov.sharedNames.map(
2780
- (n) => h(
2781
- "div",
2782
- { class: "cov-link" },
2783
- h("code", null, n.name),
2784
- h("span", { class: "muted mono" }, `${n.clusters.length} parts`),
2785
- h("span", { class: "muted" }, n.files.join(" \xB7 "))
2831
+ {
2832
+ class: "cov-link",
2833
+ onclick: () => navigate("map", { node: o.node }),
2834
+ style: { cursor: "pointer" }
2835
+ },
2836
+ h("code", null, o.node),
2837
+ h("span", { class: "muted" }, o.globs.join(" ")),
2838
+ h("span", { class: "muted mono" }, `${o.files} files`)
2786
2839
  )
2787
2840
  )
2788
2841
  )
2789
2842
  );
2790
2843
  }
2791
- function unread(cov) {
2792
- if (!cov.outsideGraph.length && !cov.files.capped && !cov.owners.length) return null;
2793
- return h(
2794
- "div",
2795
- { class: "cov-section" },
2796
- h("h3", null, "Outside the code graph"),
2797
- cov.outsideGraph.length ? h(
2798
- "div",
2799
- null,
2800
- h(
2801
- "p",
2802
- { class: "muted" },
2803
- `${cov.files.outsideGraph} files in scope are not in a language the graph reads,`,
2804
- " so they are in no part above. They are absent from the structure, not empty."
2805
- ),
2806
- h(
2807
- "div",
2808
- { class: "cov-links" },
2809
- cov.outsideGraph.slice(0, 16).map(
2810
- (e) => h(
2811
- "div",
2812
- { class: "cov-link" },
2813
- h("code", null, `.${e.extension}`),
2814
- h("span", { class: "muted mono" }, `${e.files} files`)
2815
- )
2816
- )
2817
- ),
2818
- fileList(
2819
- cov.unclustered.filter((u) => u.state === "uncovered" && u.reason === "outsideGraph").map((u) => u.file),
2820
- "uncovered"
2821
- )
2822
- ) : null,
2823
- cov.files.capped ? h(
2824
- "div",
2825
- null,
2826
- h(
2827
- "p",
2828
- { class: "muted" },
2829
- `${cov.files.capped} files in scope are in a language the graph reads, but the`,
2830
- " repo-wide file cap was hit before this scope was read, so they were never parsed."
2831
- ),
2832
- fileList(
2833
- cov.unclustered.filter((u) => u.state === "uncovered" && u.reason === "capped").map((u) => u.file),
2834
- "uncovered"
2835
- )
2836
- ) : null,
2837
- cov.owners.length ? h(
2838
- "div",
2839
- null,
2840
- h("h4", null, "What each map node owns"),
2841
- h(
2842
- "p",
2843
- { class: "muted" },
2844
- "A file counts as placed because a map node's globs match it. The globs are here",
2845
- " so a broad one is visible rather than silently inflating the count."
2846
- ),
2847
- h(
2848
- "div",
2849
- { class: "cov-links" },
2850
- cov.owners.map(
2851
- (o) => h(
2852
- "div",
2853
- {
2854
- class: "cov-link",
2855
- onclick: () => navigate("map", { node: o.node }),
2856
- style: { cursor: "pointer" }
2857
- },
2858
- h("code", null, o.node),
2859
- h("span", { class: "muted" }, o.globs.join(" ")),
2860
- h("span", { class: "muted mono" }, `${o.files} files`)
2861
- )
2862
- )
2863
- )
2864
- ) : null
2865
- );
2866
- }
2867
2844
 
2868
2845
  // src/ui/app.ts
2869
2846
  var app = document.getElementById("app");
@@ -3042,7 +3019,7 @@
3042
3019
  "div",
3043
3020
  { class: "tabs" },
3044
3021
  tabs.map(
3045
- ([v, label2]) => h("button", { class: v === current2 ? "active" : "", onclick: () => navigate(v) }, label2)
3022
+ ([v, label]) => h("button", { class: v === current2 ? "active" : "", onclick: () => navigate(v) }, label)
3046
3023
  )
3047
3024
  ),
3048
3025
  h("span", { class: "spacer" }),