thurview 0.1.4 → 0.2.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 +34 -12
- package/dist/cli.js +91 -0
- package/dist/cli.js.map +1 -1
- package/dist/graph.js +465 -0
- package/dist/graph.js.map +1 -0
- package/dist/symbols.js +1 -1
- package/dist/symbols.js.map +1 -1
- package/dist/ui/app.css +244 -17
- package/dist/ui/app.js +22 -9
- package/dist/ui/app.js.map +2 -2
- package/package.json +11 -1
- package/skills/thurview/SKILL.md +37 -5
- package/dist/ui/assets/inter-400.woff2 +0 -0
- package/dist/ui/assets/jetbrains-mono-400.woff2 +0 -0
- package/dist/ui/assets/press-start-2p-400.woff2 +0 -0
package/dist/ui/app.css
CHANGED
|
@@ -261,23 +261,45 @@ kbd {
|
|
|
261
261
|
background: var(--bg2);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/* 100dvh, not 100%: mobile browser chrome shrinks the viewport as it scrolls. */
|
|
264
265
|
#app {
|
|
265
266
|
display: flex;
|
|
266
267
|
flex-direction: column;
|
|
267
|
-
height:
|
|
268
|
+
height: 100dvh;
|
|
268
269
|
}
|
|
269
|
-
/* HUD top bar */
|
|
270
|
+
/* HUD top bar: an identity row that truncates over an actions row that never does. */
|
|
270
271
|
.topbar {
|
|
271
272
|
display: flex;
|
|
272
|
-
|
|
273
|
-
gap: 12px;
|
|
274
|
-
padding: 0 16px;
|
|
275
|
-
height: 52px;
|
|
273
|
+
flex-direction: column;
|
|
276
274
|
background: var(--hud-bg);
|
|
277
275
|
border-bottom: 2px solid var(--hud-deep);
|
|
278
276
|
box-shadow: inset 0 -1px 0 0 rgb(232 24 11 / 0.45);
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
}
|
|
278
|
+
.bar-row {
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: 12px;
|
|
282
|
+
padding: 0 16px;
|
|
283
|
+
min-height: 44px;
|
|
284
|
+
min-width: 0;
|
|
285
|
+
}
|
|
286
|
+
.bar-identity {
|
|
287
|
+
border-bottom: 1px solid var(--line2);
|
|
288
|
+
}
|
|
289
|
+
.bar-actions {
|
|
290
|
+
gap: 8px;
|
|
291
|
+
overflow-x: auto;
|
|
292
|
+
scrollbar-width: none;
|
|
293
|
+
}
|
|
294
|
+
.bar-actions::-webkit-scrollbar {
|
|
295
|
+
display: none;
|
|
296
|
+
}
|
|
297
|
+
.bar-more {
|
|
298
|
+
margin-left: 0;
|
|
299
|
+
}
|
|
300
|
+
.bar-identity > *,
|
|
301
|
+
.bar-actions > * {
|
|
302
|
+
flex: none;
|
|
281
303
|
}
|
|
282
304
|
.topbar .brand {
|
|
283
305
|
font-family: var(--font-display);
|
|
@@ -287,12 +309,14 @@ kbd {
|
|
|
287
309
|
text-shadow: var(--glow-red);
|
|
288
310
|
letter-spacing: 0.02em;
|
|
289
311
|
white-space: nowrap;
|
|
312
|
+
flex: none;
|
|
290
313
|
}
|
|
291
314
|
.topbar .brand:hover {
|
|
292
315
|
text-shadow:
|
|
293
316
|
var(--glow-red),
|
|
294
317
|
0 0 20px rgb(255 59 48 / 0.6);
|
|
295
318
|
}
|
|
319
|
+
/* Grows into the space it has, truncating only when it must; full text in the tooltip. */
|
|
296
320
|
.topbar .title {
|
|
297
321
|
font-family: var(--font-mono);
|
|
298
322
|
font-weight: 700;
|
|
@@ -300,19 +324,12 @@ kbd {
|
|
|
300
324
|
white-space: nowrap;
|
|
301
325
|
overflow: hidden;
|
|
302
326
|
text-overflow: ellipsis;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
.topbar > * {
|
|
306
|
-
flex: none;
|
|
307
|
-
}
|
|
308
|
-
.topbar .title {
|
|
309
|
-
flex: 0 1 auto;
|
|
310
|
-
min-width: 60px;
|
|
327
|
+
flex: 1 1 auto;
|
|
328
|
+
min-width: 3rem;
|
|
311
329
|
}
|
|
312
330
|
.topbar .tabs {
|
|
313
331
|
display: flex;
|
|
314
332
|
gap: 2px;
|
|
315
|
-
margin-left: 8px;
|
|
316
333
|
}
|
|
317
334
|
.tabs button {
|
|
318
335
|
border: none;
|
|
@@ -1413,3 +1430,213 @@ kbd {
|
|
|
1413
1430
|
font-family: var(--font-mono);
|
|
1414
1431
|
font-size: 13px;
|
|
1415
1432
|
}
|
|
1433
|
+
|
|
1434
|
+
/* ---- Small screens ----
|
|
1435
|
+
One column below 900px: the table-of-contents rail, the split diff and the
|
|
1436
|
+
docked side panel all assume a second column that is not there. Touch has no
|
|
1437
|
+
hover either, so anything revealed by hovering is made permanent instead. */
|
|
1438
|
+
@media (max-width: 900px) {
|
|
1439
|
+
.bar-row {
|
|
1440
|
+
gap: 8px;
|
|
1441
|
+
padding: 0 10px;
|
|
1442
|
+
min-height: 40px;
|
|
1443
|
+
}
|
|
1444
|
+
.topbar .title {
|
|
1445
|
+
font-size: 13px;
|
|
1446
|
+
}
|
|
1447
|
+
.bar-binding {
|
|
1448
|
+
display: none;
|
|
1449
|
+
}
|
|
1450
|
+
/* Only the tabs scroll: Threads and the decision must stay reachable without
|
|
1451
|
+
the reader discovering that the bar scrolls sideways. */
|
|
1452
|
+
.bar-actions {
|
|
1453
|
+
overflow: visible;
|
|
1454
|
+
}
|
|
1455
|
+
.bar-actions .tabs {
|
|
1456
|
+
flex: 1 1 auto;
|
|
1457
|
+
min-width: 0;
|
|
1458
|
+
overflow-x: auto;
|
|
1459
|
+
scrollbar-width: none;
|
|
1460
|
+
}
|
|
1461
|
+
.bar-actions .tabs::-webkit-scrollbar {
|
|
1462
|
+
display: none;
|
|
1463
|
+
}
|
|
1464
|
+
.bar-actions .spacer {
|
|
1465
|
+
display: none;
|
|
1466
|
+
}
|
|
1467
|
+
button {
|
|
1468
|
+
padding: 7px 9px;
|
|
1469
|
+
}
|
|
1470
|
+
button.small {
|
|
1471
|
+
padding: 5px 7px;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/* The rail is dropped rather than squeezed; headings stay reachable by scrolling. */
|
|
1475
|
+
.doc-layout {
|
|
1476
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1477
|
+
gap: 0;
|
|
1478
|
+
padding: 16px 14px 64px;
|
|
1479
|
+
}
|
|
1480
|
+
.toc {
|
|
1481
|
+
display: none;
|
|
1482
|
+
}
|
|
1483
|
+
.doc h1 {
|
|
1484
|
+
font-size: clamp(0.62rem, 3.4vw, 0.95rem);
|
|
1485
|
+
line-height: 1.7;
|
|
1486
|
+
}
|
|
1487
|
+
.doc h2 {
|
|
1488
|
+
font-size: clamp(0.55rem, 2.8vw, 0.8rem);
|
|
1489
|
+
margin-top: 28px;
|
|
1490
|
+
}
|
|
1491
|
+
/* Right-aligned control strip: a left gutter would sit off-screen, and a
|
|
1492
|
+
column of buttons down the left of every paragraph is noise. */
|
|
1493
|
+
.block > .block-actions {
|
|
1494
|
+
position: static;
|
|
1495
|
+
display: flex;
|
|
1496
|
+
justify-content: flex-end;
|
|
1497
|
+
opacity: 0.75;
|
|
1498
|
+
margin: 0 0 2px;
|
|
1499
|
+
}
|
|
1500
|
+
.block-actions button {
|
|
1501
|
+
width: auto;
|
|
1502
|
+
height: 22px;
|
|
1503
|
+
padding: 0 8px;
|
|
1504
|
+
font-size: 0.45rem;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/* A peek or a thread takes the screen; both already carry a close button. */
|
|
1508
|
+
.side {
|
|
1509
|
+
position: fixed;
|
|
1510
|
+
inset: 0;
|
|
1511
|
+
width: auto;
|
|
1512
|
+
max-width: none;
|
|
1513
|
+
min-width: 0;
|
|
1514
|
+
z-index: 40;
|
|
1515
|
+
border-left: none;
|
|
1516
|
+
}
|
|
1517
|
+
.resizer {
|
|
1518
|
+
display: none;
|
|
1519
|
+
}
|
|
1520
|
+
/* The sheet's only way out, so it gets a real touch target. */
|
|
1521
|
+
.side-head {
|
|
1522
|
+
min-height: 44px;
|
|
1523
|
+
}
|
|
1524
|
+
.side-head button.ghost {
|
|
1525
|
+
min-width: 40px;
|
|
1526
|
+
font-size: 0.7rem;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/* Files: the list becomes a strip above the diff. */
|
|
1530
|
+
.files-layout {
|
|
1531
|
+
flex-direction: column;
|
|
1532
|
+
}
|
|
1533
|
+
.file-list {
|
|
1534
|
+
width: 100%;
|
|
1535
|
+
min-width: 0;
|
|
1536
|
+
max-height: 30vh;
|
|
1537
|
+
border-right: none;
|
|
1538
|
+
border-bottom: 1px solid var(--hud-edge);
|
|
1539
|
+
}
|
|
1540
|
+
.file-view .file-head {
|
|
1541
|
+
flex-wrap: wrap;
|
|
1542
|
+
gap: 6px 10px;
|
|
1543
|
+
padding: 8px 10px;
|
|
1544
|
+
}
|
|
1545
|
+
.file-head .hint {
|
|
1546
|
+
display: none;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/* Map: the inspector stacks under the board. */
|
|
1550
|
+
.map-layout {
|
|
1551
|
+
flex-direction: column;
|
|
1552
|
+
}
|
|
1553
|
+
.map-canvas {
|
|
1554
|
+
padding: 14px;
|
|
1555
|
+
}
|
|
1556
|
+
.map-side {
|
|
1557
|
+
width: 100%;
|
|
1558
|
+
border-left: none;
|
|
1559
|
+
border-top: 1px solid var(--hud-edge);
|
|
1560
|
+
}
|
|
1561
|
+
.map-grid {
|
|
1562
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.commits {
|
|
1566
|
+
padding: 14px;
|
|
1567
|
+
}
|
|
1568
|
+
.home {
|
|
1569
|
+
padding: 18px 14px;
|
|
1570
|
+
}
|
|
1571
|
+
.home .item {
|
|
1572
|
+
flex-wrap: wrap;
|
|
1573
|
+
gap: 6px 10px;
|
|
1574
|
+
}
|
|
1575
|
+
.dblens .body {
|
|
1576
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1577
|
+
}
|
|
1578
|
+
.dialog {
|
|
1579
|
+
padding: 14px;
|
|
1580
|
+
}
|
|
1581
|
+
.comment-popover {
|
|
1582
|
+
width: min(92vw, 400px);
|
|
1583
|
+
}
|
|
1584
|
+
.thread-pin {
|
|
1585
|
+
flex-wrap: wrap;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/* Phones: frames give back their outer margin so the content keeps the width. */
|
|
1590
|
+
@media (max-width: 520px) {
|
|
1591
|
+
.doc-layout {
|
|
1592
|
+
padding: 12px 10px 56px;
|
|
1593
|
+
}
|
|
1594
|
+
.code-frame,
|
|
1595
|
+
.diagram {
|
|
1596
|
+
margin-left: -4px;
|
|
1597
|
+
margin-right: -4px;
|
|
1598
|
+
}
|
|
1599
|
+
.bar-rev,
|
|
1600
|
+
.topbar select {
|
|
1601
|
+
display: none;
|
|
1602
|
+
}
|
|
1603
|
+
.doc h3 {
|
|
1604
|
+
font-size: 14px;
|
|
1605
|
+
}
|
|
1606
|
+
/* Give the title the space the brand and the status chip do not need. */
|
|
1607
|
+
.topbar .brand {
|
|
1608
|
+
font-size: 0.6rem;
|
|
1609
|
+
}
|
|
1610
|
+
.bar-identity .badge {
|
|
1611
|
+
font-size: 10px;
|
|
1612
|
+
padding: 1px 5px;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/* Coarse pointers cannot hover, so affordances are shown outright. */
|
|
1617
|
+
@media (hover: none) {
|
|
1618
|
+
.block > .block-actions {
|
|
1619
|
+
opacity: 1;
|
|
1620
|
+
}
|
|
1621
|
+
.code td.ln::after {
|
|
1622
|
+
content: none;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/* Wide enough for one bar: the second row would only add height. */
|
|
1627
|
+
@media (min-width: 1100px) {
|
|
1628
|
+
.topbar {
|
|
1629
|
+
flex-direction: row;
|
|
1630
|
+
align-items: stretch;
|
|
1631
|
+
}
|
|
1632
|
+
.bar-identity {
|
|
1633
|
+
border-bottom: none;
|
|
1634
|
+
flex: 1 1 auto;
|
|
1635
|
+
min-width: 0;
|
|
1636
|
+
}
|
|
1637
|
+
.bar-actions {
|
|
1638
|
+
flex: none;
|
|
1639
|
+
padding-left: 0;
|
|
1640
|
+
overflow: visible;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
package/dist/ui/app.js
CHANGED
|
@@ -162,6 +162,10 @@
|
|
|
162
162
|
init_dom();
|
|
163
163
|
|
|
164
164
|
// src/ui/state.ts
|
|
165
|
+
var NARROW = "(max-width: 900px)";
|
|
166
|
+
function isNarrow() {
|
|
167
|
+
return window.matchMedia(NARROW).matches;
|
|
168
|
+
}
|
|
165
169
|
var state = {
|
|
166
170
|
id: "",
|
|
167
171
|
data: null,
|
|
@@ -1352,6 +1356,7 @@
|
|
|
1352
1356
|
return;
|
|
1353
1357
|
}
|
|
1354
1358
|
view.innerHTML = "";
|
|
1359
|
+
const split = state.splitDiff && inChanges && !isNarrow();
|
|
1355
1360
|
const toggle = h(
|
|
1356
1361
|
"button",
|
|
1357
1362
|
{
|
|
@@ -1391,7 +1396,7 @@
|
|
|
1391
1396
|
},
|
|
1392
1397
|
fileThreads.length ? `File comments (${fileThreads.length})` : "Comment on file"
|
|
1393
1398
|
) : null,
|
|
1394
|
-
inChanges ? toggle : h("span", { class: "badge" }, `full file at ${side2}`)
|
|
1399
|
+
inChanges ? isNarrow() ? null : toggle : h("span", { class: "badge" }, `full file at ${side2}`)
|
|
1395
1400
|
)
|
|
1396
1401
|
);
|
|
1397
1402
|
if (fileThreads.length)
|
|
@@ -1409,7 +1414,7 @@
|
|
|
1409
1414
|
const threads = (state.data?.threads ?? []).filter(
|
|
1410
1415
|
(t) => t.target.type === "file" && t.target.path === path
|
|
1411
1416
|
);
|
|
1412
|
-
const el =
|
|
1417
|
+
const el = split ? splitView(d, threads) : unifiedView(d, threads);
|
|
1413
1418
|
view.appendChild(el);
|
|
1414
1419
|
codeSelectionHandler(view);
|
|
1415
1420
|
if (line) {
|
|
@@ -2043,20 +2048,22 @@
|
|
|
2043
2048
|
`rev ${n}${n === r.revision ? " (current)" : ""}`
|
|
2044
2049
|
)
|
|
2045
2050
|
);
|
|
2046
|
-
|
|
2051
|
+
const identity = h("div", { class: "bar-row bar-identity" }, [
|
|
2047
2052
|
h("a", { href: "/", class: "brand", title: "All reviews" }, "thurview"),
|
|
2048
|
-
h("span", { class: "title" }, r.title),
|
|
2053
|
+
h("span", { class: "title", title: r.title }, r.title),
|
|
2049
2054
|
h("span", { class: `badge ${statusClass(r.status)}` }, r.status),
|
|
2050
|
-
r.revision > 1 ? revSel : h("span", { class: "badge" }, `rev ${r.revision}`),
|
|
2055
|
+
r.revision > 1 ? revSel : h("span", { class: "badge bar-rev" }, `rev ${r.revision}`),
|
|
2051
2056
|
h(
|
|
2052
2057
|
"span",
|
|
2053
2058
|
{
|
|
2054
|
-
class: "muted mono",
|
|
2059
|
+
class: "muted mono bar-binding",
|
|
2055
2060
|
style: { fontSize: "12px" },
|
|
2056
2061
|
title: `${r.pins.base} \u2192 ${r.pins.head}`
|
|
2057
2062
|
},
|
|
2058
2063
|
r.binding.kind === "pr" ? `PR #${r.binding.name}` : r.binding.name
|
|
2059
|
-
)
|
|
2064
|
+
)
|
|
2065
|
+
]);
|
|
2066
|
+
const actions = h("div", { class: "bar-row bar-actions" }, [
|
|
2060
2067
|
h(
|
|
2061
2068
|
"div",
|
|
2062
2069
|
{ class: "tabs" },
|
|
@@ -2079,10 +2086,15 @@
|
|
|
2079
2086
|
isTerminal() || state.viewingRevision !== null ? null : h(
|
|
2080
2087
|
"button",
|
|
2081
2088
|
{ class: pending ? "primary" : "ok", onclick: () => submitDialog() },
|
|
2082
|
-
pending ? `Submit
|
|
2089
|
+
pending ? `Submit${pending ? ` (${pending})` : ""}` : "Decide"
|
|
2083
2090
|
),
|
|
2084
|
-
h(
|
|
2091
|
+
h(
|
|
2092
|
+
"button",
|
|
2093
|
+
{ class: "ghost bar-more", title: "More", onclick: (e) => moreMenu(e) },
|
|
2094
|
+
"\u22EF"
|
|
2095
|
+
)
|
|
2085
2096
|
]);
|
|
2097
|
+
append(topbar, [identity, actions]);
|
|
2086
2098
|
}
|
|
2087
2099
|
function moreMenu(e) {
|
|
2088
2100
|
const r = state.data.review;
|
|
@@ -2293,6 +2305,7 @@
|
|
|
2293
2305
|
readHash();
|
|
2294
2306
|
emit("view");
|
|
2295
2307
|
});
|
|
2308
|
+
window.matchMedia(NARROW).addEventListener("change", () => emit("view"));
|
|
2296
2309
|
emit("data");
|
|
2297
2310
|
connectEvents();
|
|
2298
2311
|
}
|