thurview 0.17.2 → 0.19.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/README.md +52 -43
- package/dist/cli.js +74 -222
- package/dist/cli.js.map +1 -1
- package/dist/coverage.js +53 -148
- package/dist/coverage.js.map +1 -1
- package/dist/document/compile.js +51 -43
- package/dist/document/compile.js.map +1 -1
- package/dist/document/schema.js +25 -13
- package/dist/document/schema.js.map +1 -1
- package/dist/export.js +158 -0
- package/dist/export.js.map +1 -0
- package/dist/git.js +29 -0
- package/dist/git.js.map +1 -1
- package/dist/interfaces.js +7 -244
- package/dist/interfaces.js.map +1 -1
- package/dist/server/server.js +33 -22
- package/dist/server/server.js.map +1 -1
- package/dist/ui/app.css +14 -9
- package/dist/ui/app.js +164 -195
- package/dist/ui/app.js.map +3 -3
- package/package.json +1 -12
- package/skills/thurview/SKILL.md +51 -34
- package/skills/thurview/references/components.md +45 -22
- package/skills/thurview/references/document-authoring.md +22 -26
- package/skills/thurview/references/searching.md +74 -0
- package/skills/thurview-design/SKILL.md +21 -14
- package/skills/thurview-design/references/anchors-and-proposals.md +11 -9
- package/skills/thurview-explain/SKILL.md +47 -31
- package/skills/thurview-fix/SKILL.md +47 -42
- package/dist/graph.js +0 -568
- package/dist/graph.js.map +0 -1
package/dist/ui/app.js
CHANGED
|
@@ -125,7 +125,30 @@
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
// src/ui/api.ts
|
|
128
|
+
var published = (() => {
|
|
129
|
+
const el = document.getElementById("thurview-snapshot");
|
|
130
|
+
return el?.textContent ? JSON.parse(el.textContent) : null;
|
|
131
|
+
})();
|
|
132
|
+
function offline(url) {
|
|
133
|
+
const snap = published;
|
|
134
|
+
const u = new URL(url, "http://published.invalid");
|
|
135
|
+
const sub = u.pathname.split("/").filter(Boolean)[3];
|
|
136
|
+
const path = u.searchParams.get("path") ?? "";
|
|
137
|
+
const answer = sub === void 0 ? snap.payload : sub === "commits" ? snap.commits : sub === "presence" ? snap.payload.agent : sub === "diff" ? snap.diffs[path] : sub === "file" ? sliceLines(snap.files[`${u.searchParams.get("graph")}:${path}`], u.searchParams) : void 0;
|
|
138
|
+
if (answer === void 0) throw new Error("not in this published copy");
|
|
139
|
+
return answer;
|
|
140
|
+
}
|
|
141
|
+
function sliceLines(f, q) {
|
|
142
|
+
if (!f) return void 0;
|
|
143
|
+
const from = Math.max(1, Number(q.get("from") ?? 1));
|
|
144
|
+
const to = Math.min(f.total, Number(q.get("to") ?? f.total));
|
|
145
|
+
return { ...f, from, to, lines: f.lines.slice(from - 1, to) };
|
|
146
|
+
}
|
|
128
147
|
async function j(url, init) {
|
|
148
|
+
if (published) {
|
|
149
|
+
if (init?.method && init.method !== "GET") throw new Error("a published copy is read only");
|
|
150
|
+
return offline(url);
|
|
151
|
+
}
|
|
129
152
|
const r = await fetch(url, init);
|
|
130
153
|
const body = await r.json();
|
|
131
154
|
if (!r.ok) throw new Error(body.error ?? r.statusText);
|
|
@@ -227,7 +250,10 @@
|
|
|
227
250
|
return s === "accepted" || s === "closed";
|
|
228
251
|
}
|
|
229
252
|
function readOnly() {
|
|
230
|
-
return isTerminal() ||
|
|
253
|
+
return isTerminal() || !canWrite();
|
|
254
|
+
}
|
|
255
|
+
function canWrite() {
|
|
256
|
+
return state.viewingRevision === null && !published;
|
|
231
257
|
}
|
|
232
258
|
|
|
233
259
|
// src/ui/code.ts
|
|
@@ -615,13 +641,13 @@
|
|
|
615
641
|
return table;
|
|
616
642
|
}
|
|
617
643
|
function startLineComment(e, target, quote) {
|
|
618
|
-
if (
|
|
644
|
+
if (!canWrite()) return;
|
|
619
645
|
const el = commentPopover(target, quote?.trim() ? quote.trim() : void 0);
|
|
620
646
|
popover(el, { x: e.pageX + 8, y: e.pageY + 8 });
|
|
621
647
|
}
|
|
622
648
|
var rangeStart = null;
|
|
623
649
|
function lineClick(e, path, side2, line, table) {
|
|
624
|
-
if (
|
|
650
|
+
if (!canWrite()) return;
|
|
625
651
|
if (e.shiftKey && rangeStart && rangeStart.path === path && rangeStart.side === side2) {
|
|
626
652
|
const from = Math.min(rangeStart.line, line);
|
|
627
653
|
const to = Math.max(rangeStart.line, line);
|
|
@@ -664,8 +690,7 @@
|
|
|
664
690
|
);
|
|
665
691
|
const a = rowOf(range.startContainer);
|
|
666
692
|
const b = rowOf(range.endContainer);
|
|
667
|
-
if (!a || !b || !root.contains(a) || !root.contains(b) ||
|
|
668
|
-
return remove();
|
|
693
|
+
if (!a || !b || !root.contains(a) || !root.contains(b) || !canWrite()) return remove();
|
|
669
694
|
const table = a.closest("table");
|
|
670
695
|
if (!table || table !== b.closest("table")) return remove();
|
|
671
696
|
const path = root.closest("[data-path]")?.dataset["path"] ?? "";
|
|
@@ -708,7 +733,7 @@
|
|
|
708
733
|
const sel = window.getSelection();
|
|
709
734
|
if (sel && !sel.isCollapsed) return;
|
|
710
735
|
const word = wordAt(e);
|
|
711
|
-
if (!word) return;
|
|
736
|
+
if (!word || published) return;
|
|
712
737
|
const defs = await api.symbols(state.id, word, graph);
|
|
713
738
|
if (e.ctrlKey || e.metaKey) {
|
|
714
739
|
const d = defs[0];
|
|
@@ -1068,7 +1093,7 @@
|
|
|
1068
1093
|
} else {
|
|
1069
1094
|
g.appendChild(svg("line", { x1, y1: y2, x2: x2 + (x2 > x1 ? -4 : 4), y2 }));
|
|
1070
1095
|
}
|
|
1071
|
-
const
|
|
1096
|
+
const label = svg("text", {}, ...tspans(start, y2 - 6, rows[i].lines));
|
|
1072
1097
|
const open = (at) => {
|
|
1073
1098
|
if (m2.anchor) openAnchor(m2.anchor);
|
|
1074
1099
|
else if (m2.code)
|
|
@@ -1081,17 +1106,17 @@
|
|
|
1081
1106
|
at
|
|
1082
1107
|
);
|
|
1083
1108
|
};
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1109
|
+
label.addEventListener("click", (e) => open({ x: e.pageX, y: e.pageY + 8 }));
|
|
1110
|
+
label.setAttribute("role", "button");
|
|
1111
|
+
label.setAttribute("tabindex", "0");
|
|
1112
|
+
label.addEventListener("keydown", (e) => {
|
|
1088
1113
|
const k = e.key;
|
|
1089
1114
|
if (k !== "Enter" && k !== " ") return;
|
|
1090
1115
|
e.preventDefault();
|
|
1091
|
-
const r =
|
|
1116
|
+
const r = label.getBoundingClientRect();
|
|
1092
1117
|
open({ x: r.left + window.scrollX, y: r.bottom + window.scrollY + 8 });
|
|
1093
1118
|
});
|
|
1094
|
-
g.appendChild(
|
|
1119
|
+
g.appendChild(label);
|
|
1095
1120
|
el.appendChild(g);
|
|
1096
1121
|
});
|
|
1097
1122
|
return h(
|
|
@@ -1255,11 +1280,11 @@
|
|
|
1255
1280
|
)
|
|
1256
1281
|
)
|
|
1257
1282
|
);
|
|
1258
|
-
for (const { edge, d, back, label
|
|
1283
|
+
for (const { edge, d, back, label } of edges) {
|
|
1259
1284
|
const g = svg("g", { class: back ? "edge back" : "edge" });
|
|
1260
1285
|
g.appendChild(svg("path", { d, fill: "none", "marker-end": "url(#flow-arr)" }));
|
|
1261
1286
|
if (edge.case)
|
|
1262
|
-
g.appendChild(svg("text", { x:
|
|
1287
|
+
g.appendChild(svg("text", { x: label.x, y: label.y, "text-anchor": label.at }, edge.case));
|
|
1263
1288
|
el.appendChild(g);
|
|
1264
1289
|
}
|
|
1265
1290
|
for (const s of b.steps) {
|
|
@@ -1421,7 +1446,7 @@
|
|
|
1421
1446
|
class: threads.length ? "count" : "",
|
|
1422
1447
|
title: design ? "Comment on what this design proposes" : "Comment on the interface delta",
|
|
1423
1448
|
onclick: (e) => {
|
|
1424
|
-
if (
|
|
1449
|
+
if (!canWrite()) return;
|
|
1425
1450
|
popover(commentPopover({ type: "document", blockId: DELTA_BLOCK }), {
|
|
1426
1451
|
x: e.pageX + 10,
|
|
1427
1452
|
y: e.pageY
|
|
@@ -1454,7 +1479,7 @@
|
|
|
1454
1479
|
h(
|
|
1455
1480
|
"p",
|
|
1456
1481
|
{ class: "ifd-verdict" },
|
|
1457
|
-
delta ? delta.verdict : design ? "Unavailable: this revision declared no proposal." : "Unavailable:
|
|
1482
|
+
delta ? delta.verdict : design ? "Unavailable: this revision declared no proposal." : "Unavailable: this revision recorded no interface delta."
|
|
1458
1483
|
)
|
|
1459
1484
|
);
|
|
1460
1485
|
const entries = delta?.entries ?? [];
|
|
@@ -1491,7 +1516,7 @@
|
|
|
1491
1516
|
class: threads.length ? "count" : "",
|
|
1492
1517
|
title: "Comment on coverage",
|
|
1493
1518
|
onclick: (e) => {
|
|
1494
|
-
if (
|
|
1519
|
+
if (!canWrite()) return;
|
|
1495
1520
|
popover(commentPopover({ type: "document", blockId: COVERAGE_BLOCK }), {
|
|
1496
1521
|
x: e.pageX + 10,
|
|
1497
1522
|
y: e.pageY
|
|
@@ -1556,7 +1581,7 @@
|
|
|
1556
1581
|
class: threads.length ? "count" : "",
|
|
1557
1582
|
title: "Comment on the trust boundaries",
|
|
1558
1583
|
onclick: (e) => {
|
|
1559
|
-
if (
|
|
1584
|
+
if (!canWrite()) return;
|
|
1560
1585
|
popover(commentPopover({ type: "document", blockId: SECURITY_BLOCK }), {
|
|
1561
1586
|
x: e.pageX + 10,
|
|
1562
1587
|
y: e.pageY
|
|
@@ -1664,7 +1689,7 @@
|
|
|
1664
1689
|
class: threads.length ? "count" : "",
|
|
1665
1690
|
title: "Comment on this block",
|
|
1666
1691
|
onclick: (e) => {
|
|
1667
|
-
if (
|
|
1692
|
+
if (!canWrite()) return;
|
|
1668
1693
|
popover(commentPopover({ type: "document", blockId: b.id }), {
|
|
1669
1694
|
x: e.pageX + 10,
|
|
1670
1695
|
y: e.pageY
|
|
@@ -1759,7 +1784,7 @@
|
|
|
1759
1784
|
if (!sel || sel.isCollapsed || !sel.rangeCount) return remove();
|
|
1760
1785
|
const range = sel.getRangeAt(0);
|
|
1761
1786
|
const block = (range.commonAncestorContainer instanceof Element ? range.commonAncestorContainer : range.commonAncestorContainer.parentElement)?.closest(".block");
|
|
1762
|
-
if (!block || !docEl.contains(block) ||
|
|
1787
|
+
if (!block || !docEl.contains(block) || !canWrite()) return remove();
|
|
1763
1788
|
const quote = sel.toString().trim();
|
|
1764
1789
|
if (!quote) return remove();
|
|
1765
1790
|
const rect = range.getBoundingClientRect();
|
|
@@ -1897,12 +1922,12 @@
|
|
|
1897
1922
|
{ class: "muted" },
|
|
1898
1923
|
`${d.hunks.reduce((n, hk) => n + hk.rows.filter((r) => r.type === "add").length, 0)} added \xB7 ${d.hunks.reduce((n, hk) => n + hk.rows.filter((r) => r.type === "del").length, 0)} deleted`
|
|
1899
1924
|
),
|
|
1900
|
-
h(
|
|
1925
|
+
canWrite() ? h(
|
|
1901
1926
|
"span",
|
|
1902
1927
|
{ class: "muted hint" },
|
|
1903
1928
|
"click a line number to comment \xB7 shift-click or select text for a range"
|
|
1904
|
-
),
|
|
1905
|
-
|
|
1929
|
+
) : null,
|
|
1930
|
+
canWrite() ? h(
|
|
1906
1931
|
"button",
|
|
1907
1932
|
{
|
|
1908
1933
|
class: "small",
|
|
@@ -1959,7 +1984,7 @@
|
|
|
1959
1984
|
}
|
|
1960
1985
|
function gapRow(d, from, to, colspan, onExpand) {
|
|
1961
1986
|
if (to < from) return null;
|
|
1962
|
-
const
|
|
1987
|
+
const count2 = to - from + 1;
|
|
1963
1988
|
return h(
|
|
1964
1989
|
"tr",
|
|
1965
1990
|
{ class: "gap" },
|
|
@@ -1979,7 +2004,7 @@
|
|
|
1979
2004
|
);
|
|
1980
2005
|
}
|
|
1981
2006
|
},
|
|
1982
|
-
`\u22EF ${
|
|
2007
|
+
`\u22EF ${count2} unchanged line${count2 > 1 ? "s" : ""}`
|
|
1983
2008
|
)
|
|
1984
2009
|
);
|
|
1985
2010
|
}
|
|
@@ -2101,9 +2126,9 @@
|
|
|
2101
2126
|
let prevEnd = 0;
|
|
2102
2127
|
const gapBoth = (from, to) => {
|
|
2103
2128
|
if (to < from) return;
|
|
2104
|
-
const
|
|
2105
|
-
const lg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${
|
|
2106
|
-
const rg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${
|
|
2129
|
+
const count2 = to - from + 1;
|
|
2130
|
+
const lg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count2} unchanged`));
|
|
2131
|
+
const rg = h("tr", { class: "gap" }, h("td", { colspan: 2 }, `\u22EF ${count2} unchanged`));
|
|
2107
2132
|
const expand = async () => {
|
|
2108
2133
|
const f = await api.file(state.id, d.path, "head", from, to);
|
|
2109
2134
|
const off = gapOffset(d, from) ?? 0;
|
|
@@ -2476,7 +2501,7 @@
|
|
|
2476
2501
|
group(design ? "Context \u2014 untouched by the design" : "Context \u2014 unchanged here", cold)
|
|
2477
2502
|
);
|
|
2478
2503
|
};
|
|
2479
|
-
const
|
|
2504
|
+
const links = () => {
|
|
2480
2505
|
const here = new Set(childrenOf(all, level, map.filesByNode).map((k) => k.id));
|
|
2481
2506
|
const top = (id) => {
|
|
2482
2507
|
let x = id;
|
|
@@ -2524,7 +2549,7 @@
|
|
|
2524
2549
|
};
|
|
2525
2550
|
const draw = () => {
|
|
2526
2551
|
canvas.innerHTML = "";
|
|
2527
|
-
append(canvas, [intro2(), crumbs(), board(),
|
|
2552
|
+
append(canvas, [intro2(), crumbs(), board(), links()]);
|
|
2528
2553
|
drawSide();
|
|
2529
2554
|
};
|
|
2530
2555
|
const drawSide = () => {
|
|
@@ -2601,18 +2626,21 @@
|
|
|
2601
2626
|
);
|
|
2602
2627
|
})
|
|
2603
2628
|
) : null,
|
|
2604
|
-
h(
|
|
2629
|
+
canWrite() ? h(
|
|
2605
2630
|
"div",
|
|
2606
2631
|
{ style: { marginTop: "12px" } },
|
|
2607
2632
|
h(
|
|
2608
2633
|
"button",
|
|
2609
2634
|
{
|
|
2610
2635
|
class: "small",
|
|
2611
|
-
onclick: (e) => popover(commentPopover({ type: "map", node: n.id }), {
|
|
2636
|
+
onclick: (e) => popover(commentPopover({ type: "map", node: n.id }), {
|
|
2637
|
+
x: e.pageX,
|
|
2638
|
+
y: e.pageY + 8
|
|
2639
|
+
})
|
|
2612
2640
|
},
|
|
2613
2641
|
"Comment on this node"
|
|
2614
2642
|
)
|
|
2615
|
-
),
|
|
2643
|
+
) : null,
|
|
2616
2644
|
threads.length ? h(
|
|
2617
2645
|
"div",
|
|
2618
2646
|
{ class: "thread-pins", style: { marginTop: "10px" } },
|
|
@@ -2644,6 +2672,12 @@
|
|
|
2644
2672
|
// src/ui/views/coverage.ts
|
|
2645
2673
|
init_dom();
|
|
2646
2674
|
var SHOWN = 8;
|
|
2675
|
+
var LABELS = [
|
|
2676
|
+
["explained", "anchored in the document"],
|
|
2677
|
+
["placed", "placed on the map only"],
|
|
2678
|
+
["searched", "matched by a recorded search only"],
|
|
2679
|
+
["uncovered", "not examined"]
|
|
2680
|
+
];
|
|
2647
2681
|
function renderCoverage(root) {
|
|
2648
2682
|
const cov = state.data?.coverage;
|
|
2649
2683
|
if (!cov) {
|
|
@@ -2657,29 +2691,23 @@
|
|
|
2657
2691
|
]);
|
|
2658
2692
|
return;
|
|
2659
2693
|
}
|
|
2660
|
-
append(root, [intro(cov),
|
|
2694
|
+
append(root, [intro(cov), searches(cov), clusters(cov), owners(cov)]);
|
|
2661
2695
|
}
|
|
2696
|
+
var count = (cov, s) => cov.states[s] ?? 0;
|
|
2662
2697
|
function bar(cov) {
|
|
2663
2698
|
const total = Math.max(cov.files.total, 1);
|
|
2664
|
-
const seg = (n, cls,
|
|
2699
|
+
const seg = (n, cls, label) => n ? h("span", {
|
|
2665
2700
|
class: `cov-seg ${cls}`,
|
|
2666
2701
|
style: { width: `${n / total * 100}%` },
|
|
2667
|
-
title: `${n} ${
|
|
2702
|
+
title: `${n} ${label}`
|
|
2668
2703
|
}) : null;
|
|
2669
2704
|
return h(
|
|
2670
2705
|
"div",
|
|
2671
2706
|
{ class: "cov-bar" },
|
|
2672
|
-
seg(cov
|
|
2673
|
-
seg(cov.states.placed, "placed", "placed on the map only"),
|
|
2674
|
-
seg(cov.states.uncovered, "uncovered", "not examined")
|
|
2707
|
+
LABELS.map(([s, label]) => seg(count(cov, s), s, label))
|
|
2675
2708
|
);
|
|
2676
2709
|
}
|
|
2677
2710
|
function intro(cov) {
|
|
2678
|
-
const key = [
|
|
2679
|
-
[cov.states.explained, "explained", "anchored in the document"],
|
|
2680
|
-
[cov.states.placed, "placed", "placed on the map only"],
|
|
2681
|
-
[cov.states.uncovered, "uncovered", "not examined"]
|
|
2682
|
-
];
|
|
2683
2711
|
return h(
|
|
2684
2712
|
"div",
|
|
2685
2713
|
{ class: "cov-intro" },
|
|
@@ -2689,14 +2717,19 @@
|
|
|
2689
2717
|
null,
|
|
2690
2718
|
"A codebase does not fit in a short document, so this one selects. Here is the",
|
|
2691
2719
|
" selection, counted rather than claimed: every file in scope at the pinned commit,",
|
|
2692
|
-
" and which of the
|
|
2720
|
+
" and which of the four states it is in."
|
|
2693
2721
|
),
|
|
2694
2722
|
bar(cov),
|
|
2695
2723
|
h(
|
|
2696
2724
|
"div",
|
|
2697
2725
|
{ class: "cov-key" },
|
|
2698
|
-
|
|
2699
|
-
([
|
|
2726
|
+
LABELS.map(
|
|
2727
|
+
([s, label]) => h(
|
|
2728
|
+
"span",
|
|
2729
|
+
{ class: "cov-key-item" },
|
|
2730
|
+
h("i", { class: `cov-dot ${s}` }),
|
|
2731
|
+
`${count(cov, s)} ${label}`
|
|
2732
|
+
)
|
|
2700
2733
|
)
|
|
2701
2734
|
),
|
|
2702
2735
|
h(
|
|
@@ -2704,15 +2737,12 @@
|
|
|
2704
2737
|
{ class: "cov-facts" },
|
|
2705
2738
|
fact("scope", cov.scope === "**" ? "the whole repository" : cov.scope),
|
|
2706
2739
|
fact("commit", cov.commit.slice(0, 12)),
|
|
2707
|
-
fact("files in scope", String(cov.files.total))
|
|
2708
|
-
fact("read by the code graph", `${cov.files.inGraph} of ${cov.files.total}`),
|
|
2709
|
-
cov.unresolved ? fact("references the graph could not place", String(cov.unresolved)) : null,
|
|
2710
|
-
cov.truncated ? fact("file list capped", "the graph is partial; treat every count as a floor") : null
|
|
2740
|
+
fact("files in scope", String(cov.files.total))
|
|
2711
2741
|
)
|
|
2712
2742
|
);
|
|
2713
2743
|
}
|
|
2714
|
-
function fact(
|
|
2715
|
-
return h("div", { class: "cov-fact" }, h("b", null, value), h("span", { class: "muted" },
|
|
2744
|
+
function fact(label, value) {
|
|
2745
|
+
return h("div", { class: "cov-fact" }, h("b", null, value), h("span", { class: "muted" }, label));
|
|
2716
2746
|
}
|
|
2717
2747
|
function fileList(files, cls) {
|
|
2718
2748
|
if (!files.length) return null;
|
|
@@ -2733,9 +2763,49 @@
|
|
|
2733
2763
|
}
|
|
2734
2764
|
return wrap;
|
|
2735
2765
|
}
|
|
2766
|
+
function command(cov, s) {
|
|
2767
|
+
const quote = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
|
|
2768
|
+
const paths = s.paths.map((p) => ` ${quote(p)}`).join("");
|
|
2769
|
+
return `git grep -I -n -E -e ${quote(s.pattern)} ${cov.commit.slice(0, 12)} --${paths}`;
|
|
2770
|
+
}
|
|
2771
|
+
function searches(cov) {
|
|
2772
|
+
const list = cov.searches ?? [];
|
|
2773
|
+
if (!list.length) return null;
|
|
2774
|
+
return h(
|
|
2775
|
+
"div",
|
|
2776
|
+
{ class: "cov-section" },
|
|
2777
|
+
h("h3", null, "What the agent searched"),
|
|
2778
|
+
h(
|
|
2779
|
+
"p",
|
|
2780
|
+
{ class: "muted" },
|
|
2781
|
+
"Each search the agent recorded, re-run at the pinned commit. A search that matched",
|
|
2782
|
+
" nothing is listed too: that zero is what a claim like \u201Cnothing else calls it\u201D rests on."
|
|
2783
|
+
),
|
|
2784
|
+
h(
|
|
2785
|
+
"div",
|
|
2786
|
+
{ class: "cov-links" },
|
|
2787
|
+
list.map(
|
|
2788
|
+
(s) => h(
|
|
2789
|
+
"div",
|
|
2790
|
+
{ class: "cov-link" },
|
|
2791
|
+
h("code", null, s.key),
|
|
2792
|
+
s.why ? h("span", null, s.why) : null,
|
|
2793
|
+
h(
|
|
2794
|
+
"span",
|
|
2795
|
+
{ class: "muted mono" },
|
|
2796
|
+
`${s.hits} line${s.hits === 1 ? "" : "s"} in ${s.files.length} file${s.files.length === 1 ? "" : "s"}`
|
|
2797
|
+
),
|
|
2798
|
+
h("code", { class: "muted" }, command(cov, s)),
|
|
2799
|
+
fileList(s.files, "searched")
|
|
2800
|
+
)
|
|
2801
|
+
)
|
|
2802
|
+
)
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2736
2805
|
function clusterRow(c) {
|
|
2737
2806
|
const anchors = state.data?.document?.anchors ?? {};
|
|
2738
2807
|
const firstAnchor = c.explained.length ? Object.values(anchors).find((a) => a.peek && c.explained.includes(a.peek.file)) : void 0;
|
|
2808
|
+
const badge = (files, cls, label) => files?.length ? h("span", { class: `badge cov-b-${cls}` }, `${files.length} ${label}`) : null;
|
|
2739
2809
|
return h(
|
|
2740
2810
|
"div",
|
|
2741
2811
|
{ class: "cov-cluster" },
|
|
@@ -2743,19 +2813,14 @@
|
|
|
2743
2813
|
"div",
|
|
2744
2814
|
{ class: "cov-cluster-head" },
|
|
2745
2815
|
h("code", { class: "cov-label" }, c.label),
|
|
2746
|
-
h("span", { class: "muted" }, `${c.files} files
|
|
2816
|
+
h("span", { class: "muted" }, `${c.files} files`),
|
|
2747
2817
|
h("span", { class: "spacer" }),
|
|
2748
|
-
c.explained
|
|
2749
|
-
c.placed
|
|
2750
|
-
c.
|
|
2818
|
+
badge(c.explained, "explained", "anchored"),
|
|
2819
|
+
badge(c.placed, "placed", "placed"),
|
|
2820
|
+
badge(c.searched, "searched", "searched"),
|
|
2821
|
+
badge(c.uncovered, "uncovered", "not examined"),
|
|
2751
2822
|
firstAnchor ? h("button", { class: "small", onclick: () => openAnchorPeek(firstAnchor) }, "Peek code \u25B8") : null
|
|
2752
2823
|
),
|
|
2753
|
-
c.hubs.length ? h(
|
|
2754
|
-
"div",
|
|
2755
|
-
{ class: "muted cov-hubs" },
|
|
2756
|
-
"most referenced here: ",
|
|
2757
|
-
c.hubs.map((n) => h("code", null, n))
|
|
2758
|
-
) : null,
|
|
2759
2824
|
fileList(c.uncovered, "uncovered")
|
|
2760
2825
|
);
|
|
2761
2826
|
}
|
|
@@ -2763,153 +2828,47 @@
|
|
|
2763
2828
|
return h(
|
|
2764
2829
|
"div",
|
|
2765
2830
|
{ class: "cov-section" },
|
|
2766
|
-
h("h3", null, "By
|
|
2831
|
+
h("h3", null, "By directory"),
|
|
2767
2832
|
h(
|
|
2768
2833
|
"p",
|
|
2769
2834
|
{ class: "muted" },
|
|
2770
|
-
"
|
|
2771
|
-
" The files listed under a
|
|
2835
|
+
"The files in scope by the directory they sit in, largest first.",
|
|
2836
|
+
" The files listed under a directory are the ones nothing in this explainer reached."
|
|
2772
2837
|
),
|
|
2773
2838
|
cov.clusters.map(clusterRow)
|
|
2774
2839
|
);
|
|
2775
2840
|
}
|
|
2776
|
-
function
|
|
2777
|
-
if (!cov.
|
|
2841
|
+
function owners(cov) {
|
|
2842
|
+
if (!cov.owners.length) return null;
|
|
2778
2843
|
return h(
|
|
2779
2844
|
"div",
|
|
2780
2845
|
{ class: "cov-section" },
|
|
2781
|
-
h("h3", null, "What
|
|
2846
|
+
h("h3", null, "What each map node owns"),
|
|
2782
2847
|
h(
|
|
2783
2848
|
"p",
|
|
2784
2849
|
{ class: "muted" },
|
|
2785
|
-
"
|
|
2786
|
-
"
|
|
2850
|
+
"A file counts as placed because a map node's globs match it. The globs are here",
|
|
2851
|
+
" so a broad one is visible rather than silently inflating the count."
|
|
2787
2852
|
),
|
|
2788
2853
|
h(
|
|
2789
2854
|
"div",
|
|
2790
2855
|
{ class: "cov-links" },
|
|
2791
|
-
cov.
|
|
2792
|
-
(
|
|
2793
|
-
"div",
|
|
2794
|
-
{ class: "cov-link" },
|
|
2795
|
-
h("code", null, label(cov, l.from)),
|
|
2796
|
-
h("span", { class: "muted" }, "\u2192"),
|
|
2797
|
-
h("code", null, label(cov, l.to)),
|
|
2798
|
-
h("span", { class: "muted mono" }, `${l.references} refs`),
|
|
2799
|
-
l.bothWays ? h("span", { class: "badge" }, "both ways") : null
|
|
2800
|
-
)
|
|
2801
|
-
)
|
|
2802
|
-
)
|
|
2803
|
-
);
|
|
2804
|
-
}
|
|
2805
|
-
function label(cov, id) {
|
|
2806
|
-
return cov.clusters.find((c) => c.id === id)?.label ?? id;
|
|
2807
|
-
}
|
|
2808
|
-
function sharedNames(cov) {
|
|
2809
|
-
if (!cov.sharedNames.length) return null;
|
|
2810
|
-
return h(
|
|
2811
|
-
"div",
|
|
2812
|
-
{ class: "cov-section" },
|
|
2813
|
-
h("h3", null, "Names defined in more than one part"),
|
|
2814
|
-
h(
|
|
2815
|
-
"p",
|
|
2816
|
-
{ class: "muted" },
|
|
2817
|
-
`${cov.sharedNamesTotal} name${cov.sharedNamesTotal === 1 ? " at this commit is" : "s at this commit are"}`,
|
|
2818
|
-
" defined in two or more parts. What that means here - the same idea in two places,",
|
|
2819
|
-
" two different ideas sharing a word, or a name too common to mean anything - is",
|
|
2820
|
-
" what the code says and this page does not."
|
|
2821
|
-
),
|
|
2822
|
-
h(
|
|
2823
|
-
"div",
|
|
2824
|
-
{ class: "cov-shared" },
|
|
2825
|
-
cov.sharedNames.map(
|
|
2826
|
-
(n) => h(
|
|
2856
|
+
cov.owners.map(
|
|
2857
|
+
(o) => h(
|
|
2827
2858
|
"div",
|
|
2828
|
-
{
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2859
|
+
{
|
|
2860
|
+
class: "cov-link",
|
|
2861
|
+
onclick: () => navigate("map", { node: o.node }),
|
|
2862
|
+
style: { cursor: "pointer" }
|
|
2863
|
+
},
|
|
2864
|
+
h("code", null, o.node),
|
|
2865
|
+
h("span", { class: "muted" }, o.globs.join(" ")),
|
|
2866
|
+
h("span", { class: "muted mono" }, `${o.files} files`)
|
|
2832
2867
|
)
|
|
2833
2868
|
)
|
|
2834
2869
|
)
|
|
2835
2870
|
);
|
|
2836
2871
|
}
|
|
2837
|
-
function unread(cov) {
|
|
2838
|
-
if (!cov.outsideGraph.length && !cov.files.capped && !cov.owners.length) return null;
|
|
2839
|
-
return h(
|
|
2840
|
-
"div",
|
|
2841
|
-
{ class: "cov-section" },
|
|
2842
|
-
h("h3", null, "Outside the code graph"),
|
|
2843
|
-
cov.outsideGraph.length ? h(
|
|
2844
|
-
"div",
|
|
2845
|
-
null,
|
|
2846
|
-
h(
|
|
2847
|
-
"p",
|
|
2848
|
-
{ class: "muted" },
|
|
2849
|
-
`${cov.files.outsideGraph} files in scope are not in a language the graph reads,`,
|
|
2850
|
-
" so they are in no part above. They are absent from the structure, not empty."
|
|
2851
|
-
),
|
|
2852
|
-
h(
|
|
2853
|
-
"div",
|
|
2854
|
-
{ class: "cov-links" },
|
|
2855
|
-
cov.outsideGraph.slice(0, 16).map(
|
|
2856
|
-
(e) => h(
|
|
2857
|
-
"div",
|
|
2858
|
-
{ class: "cov-link" },
|
|
2859
|
-
h("code", null, `.${e.extension}`),
|
|
2860
|
-
h("span", { class: "muted mono" }, `${e.files} files`)
|
|
2861
|
-
)
|
|
2862
|
-
)
|
|
2863
|
-
),
|
|
2864
|
-
fileList(
|
|
2865
|
-
cov.unclustered.filter((u) => u.state === "uncovered" && u.reason === "outsideGraph").map((u) => u.file),
|
|
2866
|
-
"uncovered"
|
|
2867
|
-
)
|
|
2868
|
-
) : null,
|
|
2869
|
-
cov.files.capped ? h(
|
|
2870
|
-
"div",
|
|
2871
|
-
null,
|
|
2872
|
-
h(
|
|
2873
|
-
"p",
|
|
2874
|
-
{ class: "muted" },
|
|
2875
|
-
`${cov.files.capped} files in scope are in a language the graph reads, but the`,
|
|
2876
|
-
" repo-wide file cap was hit before this scope was read, so they were never parsed."
|
|
2877
|
-
),
|
|
2878
|
-
fileList(
|
|
2879
|
-
cov.unclustered.filter((u) => u.state === "uncovered" && u.reason === "capped").map((u) => u.file),
|
|
2880
|
-
"uncovered"
|
|
2881
|
-
)
|
|
2882
|
-
) : null,
|
|
2883
|
-
cov.owners.length ? h(
|
|
2884
|
-
"div",
|
|
2885
|
-
null,
|
|
2886
|
-
h("h4", null, "What each map node owns"),
|
|
2887
|
-
h(
|
|
2888
|
-
"p",
|
|
2889
|
-
{ class: "muted" },
|
|
2890
|
-
"A file counts as placed because a map node's globs match it. The globs are here",
|
|
2891
|
-
" so a broad one is visible rather than silently inflating the count."
|
|
2892
|
-
),
|
|
2893
|
-
h(
|
|
2894
|
-
"div",
|
|
2895
|
-
{ class: "cov-links" },
|
|
2896
|
-
cov.owners.map(
|
|
2897
|
-
(o) => h(
|
|
2898
|
-
"div",
|
|
2899
|
-
{
|
|
2900
|
-
class: "cov-link",
|
|
2901
|
-
onclick: () => navigate("map", { node: o.node }),
|
|
2902
|
-
style: { cursor: "pointer" }
|
|
2903
|
-
},
|
|
2904
|
-
h("code", null, o.node),
|
|
2905
|
-
h("span", { class: "muted" }, o.globs.join(" ")),
|
|
2906
|
-
h("span", { class: "muted mono" }, `${o.files} files`)
|
|
2907
|
-
)
|
|
2908
|
-
)
|
|
2909
|
-
)
|
|
2910
|
-
) : null
|
|
2911
|
-
);
|
|
2912
|
-
}
|
|
2913
2872
|
|
|
2914
2873
|
// src/ui/app.ts
|
|
2915
2874
|
var app = document.getElementById("app");
|
|
@@ -3069,10 +3028,10 @@
|
|
|
3069
3028
|
)
|
|
3070
3029
|
);
|
|
3071
3030
|
const identity = h("div", { class: "bar-row bar-identity" }, [
|
|
3072
|
-
h("a", { href: "/", class: "brand", title: "All reviews" }, "thurview"),
|
|
3031
|
+
published ? h("span", { class: "brand" }, "thurview") : h("a", { href: "/", class: "brand", title: "All reviews" }, "thurview"),
|
|
3073
3032
|
h("span", { class: "title", title: r.title }, r.title),
|
|
3074
3033
|
h("span", { class: `badge ${statusClass2(r.status)}` }, r.status),
|
|
3075
|
-
r.revision > 1 ? revSel : h("span", { class: "badge bar-rev" }, `rev ${r.revision}`),
|
|
3034
|
+
r.revision > 1 && !published ? revSel : h("span", { class: "badge bar-rev" }, `rev ${r.revision}`),
|
|
3076
3035
|
h(
|
|
3077
3036
|
"span",
|
|
3078
3037
|
{
|
|
@@ -3088,7 +3047,7 @@
|
|
|
3088
3047
|
"div",
|
|
3089
3048
|
{ class: "tabs" },
|
|
3090
3049
|
tabs.map(
|
|
3091
|
-
([v,
|
|
3050
|
+
([v, label]) => h("button", { class: v === current2 ? "active" : "", onclick: () => navigate(v) }, label)
|
|
3092
3051
|
)
|
|
3093
3052
|
),
|
|
3094
3053
|
h("span", { class: "spacer" }),
|
|
@@ -3103,12 +3062,12 @@
|
|
|
3103
3062
|
},
|
|
3104
3063
|
`Threads${open ? ` \xB7 ${open}` : ""}`
|
|
3105
3064
|
),
|
|
3106
|
-
|
|
3065
|
+
readOnly() ? null : h(
|
|
3107
3066
|
"button",
|
|
3108
3067
|
{ class: pending ? "primary" : "ok", onclick: () => submitDialog() },
|
|
3109
3068
|
pending ? `Submit${pending ? ` (${pending})` : ""}` : "Decide"
|
|
3110
3069
|
),
|
|
3111
|
-
h(
|
|
3070
|
+
published ? null : h(
|
|
3112
3071
|
"button",
|
|
3113
3072
|
{ class: "ghost bar-more", title: "More", onclick: (e) => moreMenu(e) },
|
|
3114
3073
|
"\u22EF"
|
|
@@ -3215,7 +3174,12 @@
|
|
|
3215
3174
|
banner.hidden = true;
|
|
3216
3175
|
clear(banner);
|
|
3217
3176
|
const d = state.data;
|
|
3218
|
-
if (
|
|
3177
|
+
if (published) {
|
|
3178
|
+
banner.hidden = false;
|
|
3179
|
+
banner.append(
|
|
3180
|
+
`Published copy of revision ${d.review.revision}, ${d.review.status}. Read only: nothing here reaches the agent.`
|
|
3181
|
+
);
|
|
3182
|
+
} else if (state.viewingRevision !== null) {
|
|
3219
3183
|
banner.hidden = false;
|
|
3220
3184
|
banner.append(
|
|
3221
3185
|
`Viewing revision ${state.viewingRevision} of ${d.review.revision}. Read only.`,
|
|
@@ -3344,11 +3308,16 @@
|
|
|
3344
3308
|
});
|
|
3345
3309
|
window.matchMedia(NARROW).addEventListener("change", () => emit("view"));
|
|
3346
3310
|
emit("data");
|
|
3311
|
+
if (published) {
|
|
3312
|
+
document.documentElement.classList.add("published");
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3347
3315
|
connectEvents();
|
|
3348
3316
|
pollPresence();
|
|
3349
3317
|
}
|
|
3350
3318
|
var m = /^\/review\/([^/]+)/.exec(location.pathname);
|
|
3351
|
-
if (
|
|
3319
|
+
if (published) void reviewPage(published.payload.review.id);
|
|
3320
|
+
else if (m) void reviewPage(m[1]);
|
|
3352
3321
|
else void home();
|
|
3353
3322
|
})();
|
|
3354
3323
|
//# sourceMappingURL=app.js.map
|