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/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: 100%;
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
- align-items: center;
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
- flex-wrap: nowrap;
280
- overflow: hidden;
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
- max-width: 28vw;
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 = state.splitDiff && inChanges ? splitView(d, threads) : unifiedView(d, threads);
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
- append(topbar, [
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 review (${pending})` : "Approve / Request changes"
2089
+ pending ? `Submit${pending ? ` (${pending})` : ""}` : "Decide"
2083
2090
  ),
2084
- h("button", { class: "ghost", title: "More", onclick: (e) => moreMenu(e) }, "\u22EF")
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
  }