@kud/gh-ink 0.8.1 → 0.9.1

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/index.d.ts CHANGED
@@ -217,7 +217,7 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
217
217
  actions: Action[];
218
218
  cursor: number;
219
219
  }) => React.JSX.Element;
220
- declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, extensions, tabHelp, }: {
220
+ declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, extensions, tabHelp, emptyHint, }: {
221
221
  fetcher: () => Promise<{
222
222
  sections: Section[];
223
223
  login: string;
@@ -229,6 +229,7 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIn
229
229
  isWorkRepo?: (repo: string) => boolean;
230
230
  initialIncludeWork?: boolean;
231
231
  tabHelp?: [string, string][];
232
+ emptyHint?: string;
232
233
  jiraBase?: string;
233
234
  jiraKeyRe?: RegExp;
234
235
  jiraTransitions?: JiraTransition[];
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import React2, { useState, useRef, useEffect } from 'react';
1
+ import React2, { createContext, useState, useRef, useEffect, useContext } from 'react';
2
2
  import { useWindowSize, useInput, Text, Box } from 'ink';
3
- import { colors, ScrollView, TextInput, LoadingScreen, Switch, useListCursor, Tabs, FooterHints } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, LoadingScreen, Switch, StatusMessage, FooterHints, useListCursor, Tabs } from '@kud/ink-ui';
4
4
  import { isPassCheck, isFailCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
  import { $ } from 'zx';
@@ -1066,6 +1066,27 @@ var FRAME_PAD_X = 1;
1066
1066
  var FRAME_CHROME_COLS = 2 + FRAME_PAD_X * 2;
1067
1067
  var COLS = (process.stdout.columns ?? 120) - FRAME_CHROME_COLS;
1068
1068
  var MODAL_CHROME_COLS = 2 + 1 * 2;
1069
+ var OVERLAY_BG = "#22222e";
1070
+ var DimContext = createContext(false);
1071
+ var Text2 = ({ children, ...props }) => {
1072
+ const dimmed = useContext(DimContext);
1073
+ return dimmed ? /* @__PURE__ */ jsx(Text, { ...props, bold: false, dimColor: true, children }) : /* @__PURE__ */ jsx(Text, { ...props, children });
1074
+ };
1075
+ var Backdrop = ({
1076
+ dimmed,
1077
+ absolute,
1078
+ height,
1079
+ children
1080
+ }) => /* @__PURE__ */ jsx(DimContext.Provider, { value: dimmed, children: absolute ? /* @__PURE__ */ jsx(
1081
+ Box,
1082
+ {
1083
+ position: "absolute",
1084
+ flexDirection: "column",
1085
+ width: "100%",
1086
+ height,
1087
+ children
1088
+ }
1089
+ ) : children });
1069
1090
  var topLevelCount = (s) => s.items.filter(
1070
1091
  (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && !i.indent
1071
1092
  ).length;
@@ -1342,13 +1363,14 @@ var InboxHeader = ({
1342
1363
  brand,
1343
1364
  work,
1344
1365
  loading,
1366
+ quiet,
1345
1367
  refreshing,
1346
1368
  hasPending,
1347
1369
  fetchedAt
1348
1370
  }) => {
1349
1371
  const total = sections.reduce((n, s) => n + topLevelCount(s), 0);
1350
- const countSeg = loading ? " loading\u2026 " : ` ${String(total).padStart(3)} item${total !== 1 ? "s" : ""} \xB7 `;
1351
- const userSeg = loading ? "" : `@${login} `;
1372
+ const countSeg = loading ? " loading\u2026 " : quiet ? " " : ` ${String(total).padStart(3)} item${total !== 1 ? "s" : ""} \xB7 `;
1373
+ const userSeg = loading || quiet ? "" : `@${login} `;
1352
1374
  const workLabel = work === void 0 ? "" : " w work \u25CF\u2500\u25CB home ";
1353
1375
  const [statusText, statusColor] = hasPending ? ["\u25CF new \xB7 r apply", "#FF8700"] : refreshing ? ["\u21BB refreshing\u2026", "cyan"] : fetchedAt ? [`updated ${agoText(fetchedAt)}`, void 0] : ["", void 0];
1354
1376
  const statusSeg = statusText ? statusText + " " : "";
@@ -1357,16 +1379,16 @@ var InboxHeader = ({
1357
1379
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - statusSeg.length
1358
1380
  );
1359
1381
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1360
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", bold: true, children: brand }),
1361
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: countSeg }),
1362
- userSeg ? /* @__PURE__ */ jsx(Text, { children: userSeg }) : null,
1382
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: brand }),
1383
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: countSeg }),
1384
+ userSeg ? /* @__PURE__ */ jsx(Text2, { children: userSeg }) : null,
1363
1385
  work !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1364
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " w " }),
1386
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " w " }),
1365
1387
  /* @__PURE__ */ jsx(Switch, { left: "work", right: "home", value: work ? "left" : "right" }),
1366
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " })
1388
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " })
1367
1389
  ] }) : null,
1368
1390
  statusText ? /* @__PURE__ */ jsx(
1369
- Text,
1391
+ Text2,
1370
1392
  {
1371
1393
  color: statusColor,
1372
1394
  dimColor: !statusColor,
@@ -1374,7 +1396,7 @@ var InboxHeader = ({
1374
1396
  children: statusSeg
1375
1397
  }
1376
1398
  ) : null,
1377
- /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: "\u254C".repeat(fill) })
1399
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", dimColor: true, children: "\u254C".repeat(fill) })
1378
1400
  ] });
1379
1401
  };
1380
1402
  var toCiStatusState = (status) => status ? { kind: "ready", status } : { kind: "error" };
@@ -1397,30 +1419,30 @@ var CiStatusLine = ({
1397
1419
  const name = job ?? "ci";
1398
1420
  if (state.kind === "loading")
1399
1421
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1400
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
1401
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${name} loading\u2026` })
1422
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " \xB7 " }),
1423
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `${name} loading\u2026` })
1402
1424
  ] });
1403
1425
  if (state.kind === "error")
1404
1426
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1405
- /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: " \u2717 " }),
1406
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${name} no build / not configured` })
1427
+ /* @__PURE__ */ jsx(Text2, { color: "red", bold: true, children: " \u2717 " }),
1428
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `${name} no build / not configured` })
1407
1429
  ] });
1408
1430
  const { status } = state;
1409
1431
  const [, color] = jenkinsResultDisplay(status.result, status.building);
1410
1432
  const label = status.building ? "BUILDING" : status.result;
1411
1433
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1412
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
1413
- /* @__PURE__ */ jsx(Text, { color, bold: true, children: "\u25CF " }),
1414
- /* @__PURE__ */ jsx(Text, { bold: true, children: status.job }),
1415
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + label }),
1416
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", children: " #" + status.buildNumber }),
1417
- status.age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + status.age }) : null
1434
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " }),
1435
+ /* @__PURE__ */ jsx(Text2, { color, bold: true, children: "\u25CF " }),
1436
+ /* @__PURE__ */ jsx(Text2, { bold: true, children: status.job }),
1437
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label }),
1438
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " #" + status.buildNumber }),
1439
+ status.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + status.age }) : null
1418
1440
  ] });
1419
1441
  };
1420
1442
  var RepoHeaderRow = ({ repo, gap }) => {
1421
1443
  const label = `\u2500\u2500 ${repo} `;
1422
1444
  const fill = Math.max(4, 46 - label.length);
1423
- return /* @__PURE__ */ jsx(Box, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + label + "\u2500".repeat(fill) }) });
1445
+ return /* @__PURE__ */ jsx(Box, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label + "\u2500".repeat(fill) }) });
1424
1446
  };
1425
1447
  var ItemRow = ({
1426
1448
  item,
@@ -1432,29 +1454,29 @@ var ItemRow = ({
1432
1454
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false });
1433
1455
  if (item.kind === "subgroup-header")
1434
1456
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1435
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", bold: true, children: " \xBB " }),
1436
- /* @__PURE__ */ jsx(Text, { bold: true, children: item.label })
1457
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: " \xBB " }),
1458
+ /* @__PURE__ */ jsx(Text2, { bold: true, children: item.label })
1437
1459
  ] });
1438
1460
  if (item.kind === "show-more")
1439
1461
  return /* @__PURE__ */ jsxs(Box, { children: [
1440
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
1441
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2514\u2500 " }),
1442
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: active ? "\u21B5 " : " " }),
1443
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `+${item.hidden.length} more` })
1462
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1463
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }),
1464
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1465
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `+${item.hidden.length} more` })
1444
1466
  ] });
1445
1467
  if (item.kind === "show-less")
1446
1468
  return /* @__PURE__ */ jsxs(Box, { children: [
1447
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
1448
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2514\u2500 " }),
1449
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: active ? "\u21B5 " : " " }),
1450
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "show less" })
1469
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1470
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }),
1471
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1472
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "show less" })
1451
1473
  ] });
1452
1474
  if (item.kind === "jira") {
1453
1475
  const titleMax2 = Math.max(20, COLS - item.key.length - 10);
1454
1476
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1455
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
1456
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", bold: active, children: item.key + " " }),
1457
- /* @__PURE__ */ jsx(Text, { bold: active, children: truncate(item.summary, titleMax2) })
1477
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1478
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
1479
+ /* @__PURE__ */ jsx(Text2, { bold: active, children: truncate(item.summary, titleMax2) })
1458
1480
  ] });
1459
1481
  }
1460
1482
  const { glyph: icon, color } = healthDisplay[item.health];
@@ -1471,16 +1493,16 @@ var ItemRow = ({
1471
1493
  const fixedWidth = 2 + (item.indent ? 3 : 0) + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
1472
1494
  const titleMax = Math.max(20, COLS - fixedWidth);
1473
1495
  return /* @__PURE__ */ jsxs(Box, { children: [
1474
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
1475
- item.indent ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2514\u2500 " }) : null,
1476
- /* @__PURE__ */ jsx(Text, { color, bold: true, children: icon + " " }),
1477
- /* @__PURE__ */ jsx(Text, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1478
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", children: numStr }),
1479
- /* @__PURE__ */ jsx(Text, { bold: active, children: truncate(item.title, titleMax) + " " }),
1480
- repoLabel ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: repoLabel }) : null,
1481
- unresolvedLabel ? /* @__PURE__ */ jsx(Text, { bold: true, color: "#FF8700", children: " " + unresolvedLabel }) : null,
1482
- showAuthor ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1483
- item.age ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + item.age }) : null
1496
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1497
+ item.indent ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }) : null,
1498
+ /* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
1499
+ /* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1500
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
1501
+ /* @__PURE__ */ jsx(Text2, { bold: active, children: truncate(item.title, titleMax) + " " }),
1502
+ repoLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: repoLabel }) : null,
1503
+ unresolvedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: "#FF8700", children: " " + unresolvedLabel }) : null,
1504
+ showAuthor ? /* @__PURE__ */ jsx(Text2, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1505
+ item.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + item.age }) : null
1484
1506
  ] });
1485
1507
  };
1486
1508
  var useActionMenu = () => {
@@ -1522,18 +1544,19 @@ var ActionMenu = ({
1522
1544
  flexDirection: "column",
1523
1545
  borderStyle: "round",
1524
1546
  borderColor: "cyan",
1547
+ backgroundColor: OVERLAY_BG,
1525
1548
  paddingX: 1,
1526
1549
  marginTop: 1,
1527
1550
  children: [
1528
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: title }),
1529
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(32) }),
1551
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: title }),
1552
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(32) }),
1530
1553
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
1531
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: i === cursor ? "\u276F " : " " }),
1532
- /* @__PURE__ */ jsx(Text, { bold: i === cursor, children: a.label }),
1533
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + a.hint })
1554
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: i === cursor ? "\u276F " : " " }),
1555
+ /* @__PURE__ */ jsx(Text2, { bold: i === cursor, children: a.label }),
1556
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + a.hint })
1534
1557
  ] }, a.label)),
1535
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(32) }),
1536
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
1558
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(32) }),
1559
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2191\u2193 navigate \u21B5 confirm esc cancel" })
1537
1560
  ]
1538
1561
  }
1539
1562
  );
@@ -1570,10 +1593,10 @@ var LegendGroup = ({
1570
1593
  flexDirection: "column",
1571
1594
  marginTop: index === 0 ? 0 : 1,
1572
1595
  children: [
1573
- /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: col.title }),
1596
+ /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: col.title }),
1574
1597
  col.rows.map((row) => /* @__PURE__ */ jsxs(Box, { children: [
1575
- /* @__PURE__ */ jsx(Text, { color: row.color, bold: row.bold, children: row.cell.padEnd(cellWidth) }),
1576
- /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: row.label })
1598
+ /* @__PURE__ */ jsx(Text2, { color: row.color, bold: row.bold, children: row.cell.padEnd(cellWidth) }),
1599
+ /* @__PURE__ */ jsx(Text2, { wrap: "truncate-end", children: row.label })
1577
1600
  ] }, col.title + row.cell + row.label))
1578
1601
  ]
1579
1602
  },
@@ -1661,10 +1684,11 @@ var HelpModal = ({
1661
1684
  flexDirection: "column",
1662
1685
  borderStyle: "round",
1663
1686
  borderColor: "cyan",
1687
+ backgroundColor: OVERLAY_BG,
1664
1688
  paddingX: 1,
1665
1689
  width: contentWidth + MODAL_CHROME_COLS,
1666
1690
  children: [
1667
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Legend" }),
1691
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: "Legend" }),
1668
1692
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
1669
1693
  LegendGroup,
1670
1694
  {
@@ -1674,8 +1698,8 @@ var HelpModal = ({
1674
1698
  },
1675
1699
  group[0]?.title ?? index
1676
1700
  )) }),
1677
- tabHelp ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Each item lands in the FIRST tab that claims it, so counts are residuals rather than totals." }) : null,
1678
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "esc \xB7 ? close" })
1701
+ tabHelp ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "Each item lands in the FIRST tab that claims it, so counts are residuals rather than totals." }) : null,
1702
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "esc \xB7 ? close" })
1679
1703
  ]
1680
1704
  }
1681
1705
  );
@@ -1686,16 +1710,17 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
1686
1710
  flexDirection: "column",
1687
1711
  borderStyle: "round",
1688
1712
  borderColor: "cyan",
1713
+ backgroundColor: OVERLAY_BG,
1689
1714
  paddingX: 1,
1690
1715
  width: Math.min(COLS, 78),
1691
1716
  children: [
1692
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
1693
- /* @__PURE__ */ jsx(Text, { children: item.title }),
1717
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
1718
+ /* @__PURE__ */ jsx(Text2, { children: item.title }),
1694
1719
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1695
- /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: section.heading }),
1696
- section.lines.map((line, i) => /* @__PURE__ */ jsx(Text, { children: " " + line }, i))
1720
+ /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: section.heading }),
1721
+ section.lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { children: " " + line }, i))
1697
1722
  ] }, section.heading)),
1698
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "esc \xB7 e close" }) })
1723
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "esc \xB7 e close" }) })
1699
1724
  ]
1700
1725
  }
1701
1726
  );
@@ -1717,24 +1742,25 @@ var RepoPicker = ({
1717
1742
  flexDirection: "column",
1718
1743
  borderStyle: "round",
1719
1744
  borderColor: "cyan",
1745
+ backgroundColor: OVERLAY_BG,
1720
1746
  paddingX: 1,
1721
1747
  minWidth: 42,
1722
1748
  children: [
1723
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: `Filter by repo (${selected.size} on)` }),
1724
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(36) }),
1749
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: `Filter by repo (${selected.size} on)` }),
1750
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(36) }),
1725
1751
  visible.map((repo, i) => {
1726
1752
  const idx = start + i;
1727
1753
  const on = selected.has(repo);
1728
1754
  const active = idx === cursor;
1729
1755
  return /* @__PURE__ */ jsxs(Box, { children: [
1730
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
1731
- /* @__PURE__ */ jsx(Text, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
1732
- /* @__PURE__ */ jsx(Text, { bold: active, children: repo })
1756
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1757
+ /* @__PURE__ */ jsx(Text2, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
1758
+ /* @__PURE__ */ jsx(Text2, { bold: active, children: repo })
1733
1759
  ] }, repo);
1734
1760
  }),
1735
- repos.length > budget ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
1736
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(36) }),
1737
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
1761
+ repos.length > budget ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` \u2026 ${repos.length} repos total` }) : null,
1762
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2500".repeat(36) }),
1763
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "space toggle \xB7 a clear all \xB7 \u21B5/esc done" })
1738
1764
  ]
1739
1765
  }
1740
1766
  );
@@ -2146,6 +2172,22 @@ var BrowseScreen = ({
2146
2172
  (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
2147
2173
  ).length;
2148
2174
  if (hidden) return null;
2175
+ const overlay = help ? /* @__PURE__ */ jsx(
2176
+ HelpModal,
2177
+ {
2178
+ workToggle,
2179
+ extensions,
2180
+ hasJira: !!jiraBase,
2181
+ tabHelp
2182
+ }
2183
+ ) : explain && activeItem && (activeItem.kind === "pr" || activeItem.kind === "issue") ? /* @__PURE__ */ jsx(ExplainModal, { item: activeItem, login }) : repoPicker ? /* @__PURE__ */ jsx(
2184
+ RepoPicker,
2185
+ {
2186
+ repos: allRepos,
2187
+ selected: repoFilter,
2188
+ cursor: Math.min(repoCursor, Math.max(0, allRepos.length - 1))
2189
+ }
2190
+ ) : menu.actions && activeItem && activeItem.kind !== "repo-header" ? /* @__PURE__ */ jsx(ActionMenu, { item: activeItem, actions: menu.actions, cursor: menu.cursor }) : null;
2149
2191
  return /* @__PURE__ */ jsxs(
2150
2192
  Box,
2151
2193
  {
@@ -2181,110 +2223,87 @@ var BrowseScreen = ({
2181
2223
  }
2182
2224
  ) }),
2183
2225
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2184
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " / " }),
2185
- /* @__PURE__ */ jsx(Text, { children: search }),
2186
- searchInput ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258F" }) : null,
2226
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: " / " }),
2227
+ /* @__PURE__ */ jsx(Text2, { children: search }),
2228
+ searchInput ? /* @__PURE__ */ jsx(Text2, { color: "cyan", children: "\u258F" }) : null,
2187
2229
  /* @__PURE__ */ jsx(
2188
- Text,
2230
+ Text2,
2189
2231
  {
2190
2232
  dimColor: true,
2191
2233
  children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}`
2192
2234
  }
2193
2235
  )
2194
2236
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2195
- /* @__PURE__ */ jsx(Text, { color: "#FF8700", children: " \u25C9 " }),
2196
- /* @__PURE__ */ jsx(Text, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
2197
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " f edit \xB7 esc clear" })
2237
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " \u25C9 " }),
2238
+ /* @__PURE__ */ jsx(Text2, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
2239
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " f edit \xB7 esc clear" })
2198
2240
  ] }) : null,
2199
- help ? /* @__PURE__ */ jsx(
2200
- Box,
2201
- {
2202
- minHeight: listHeight,
2203
- flexDirection: "column",
2204
- justifyContent: "center",
2205
- alignItems: "center",
2206
- children: /* @__PURE__ */ jsx(
2207
- HelpModal,
2208
- {
2209
- workToggle,
2210
- extensions,
2211
- hasJira: !!jiraBase,
2212
- tabHelp
2213
- }
2214
- )
2215
- }
2216
- ) : explain && activeItem && (activeItem.kind === "pr" || activeItem.kind === "issue") ? /* @__PURE__ */ jsx(
2217
- Box,
2218
- {
2219
- minHeight: listHeight,
2220
- flexDirection: "column",
2221
- justifyContent: "center",
2222
- alignItems: "center",
2223
- children: /* @__PURE__ */ jsx(ExplainModal, { item: activeItem, login })
2224
- }
2225
- ) : repoPicker ? /* @__PURE__ */ jsx(
2226
- Box,
2227
- {
2228
- minHeight: listHeight,
2229
- flexDirection: "column",
2230
- justifyContent: "center",
2231
- alignItems: "center",
2232
- children: /* @__PURE__ */ jsx(
2233
- RepoPicker,
2234
- {
2235
- repos: allRepos,
2236
- selected: repoFilter,
2237
- cursor: Math.min(repoCursor, Math.max(0, allRepos.length - 1))
2238
- }
2239
- )
2240
- }
2241
- ) : menu.actions && activeItem && activeItem.kind !== "repo-header" ? /* @__PURE__ */ jsx(
2242
- Box,
2243
- {
2244
- minHeight: listHeight,
2245
- flexDirection: "column",
2246
- justifyContent: "center",
2247
- alignItems: "center",
2248
- children: /* @__PURE__ */ jsx(
2249
- ActionMenu,
2241
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", minHeight: listHeight, children: [
2242
+ /* @__PURE__ */ jsxs(Backdrop, { dimmed: !!overlay, absolute: !!overlay, height: listHeight, children: [
2243
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleItems.map((item, i) => /* @__PURE__ */ jsx(
2244
+ ItemRow,
2250
2245
  {
2251
- item: activeItem,
2252
- actions: menu.actions,
2253
- cursor: menu.cursor
2254
- }
2255
- )
2256
- }
2257
- ) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", minHeight: listHeight, children: [
2258
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleItems.map((item, i) => /* @__PURE__ */ jsx(
2259
- ItemRow,
2246
+ item,
2247
+ active: viewStart + i === cursor,
2248
+ login,
2249
+ gap: viewStart + i > 0 && (item.kind === "repo-header" || item.kind === "subgroup-header" || // A header is always followed by a blank line before its
2250
+ // first child — in Other PRs that's "free" because the
2251
+ // child is itself a repo-header (gap above). A jira row
2252
+ // has no such stand-in, so it needs this explicitly. Never
2253
+ // applies between two tickets/PRs only right after a
2254
+ // header.
2255
+ item.kind === "jira" && ["repo-header", "subgroup-header"].includes(
2256
+ section.items[viewStart + i - 1]?.kind
2257
+ ))
2258
+ },
2259
+ `${viewStart + i}:${item.kind === "jira" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
2260
+ )) }),
2261
+ hasMore && /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
2262
+ " ",
2263
+ "\u2193 ",
2264
+ section.items.length - viewStart - visibleCount,
2265
+ " more"
2266
+ ] })
2267
+ ] }),
2268
+ overlay ? /* @__PURE__ */ jsx(
2269
+ Box,
2260
2270
  {
2261
- item,
2262
- active: viewStart + i === cursor,
2263
- login,
2264
- gap: viewStart + i > 0 && (item.kind === "repo-header" || item.kind === "subgroup-header" || // A header is always followed by a blank line before its
2265
- // first child — in Other PRs that's "free" because the
2266
- // child is itself a repo-header (gap above). A jira row
2267
- // has no such stand-in, so it needs this explicitly. Never
2268
- // applies between two tickets/PRs — only right after a
2269
- // header.
2270
- item.kind === "jira" && ["repo-header", "subgroup-header"].includes(
2271
- section.items[viewStart + i - 1]?.kind
2272
- ))
2273
- },
2274
- `${viewStart + i}:${item.kind === "jira" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
2275
- )) }),
2276
- hasMore && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2277
- " ",
2278
- "\u2193 ",
2279
- section.items.length - viewStart - visibleCount,
2280
- " more"
2281
- ] })
2271
+ flexGrow: 1,
2272
+ flexDirection: "column",
2273
+ justifyContent: "center",
2274
+ alignItems: "center",
2275
+ children: overlay
2276
+ }
2277
+ ) : null
2282
2278
  ] }),
2283
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text, { color: "green", children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
2279
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text2, { color: "green", children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
2284
2280
  ]
2285
2281
  }
2286
2282
  );
2287
2283
  };
2284
+ var NoRowsScreen = ({
2285
+ reason,
2286
+ detail,
2287
+ onRetry
2288
+ }) => {
2289
+ useInput((input) => {
2290
+ if (input === "q") process.exit(0);
2291
+ if (input === "r") onRetry();
2292
+ });
2293
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2294
+ /* @__PURE__ */ jsx(StatusMessage, { variant: reason === "empty" ? "info" : "error", children: reason === "empty" ? "Nothing open." : `Fetch failed \u2014 ${detail ?? "no reason reported"}` }),
2295
+ reason === "empty" && detail ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: detail }) }) : null,
2296
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
2297
+ FooterHints,
2298
+ {
2299
+ hints: [
2300
+ ["r", "retry"],
2301
+ ["q", "quit"]
2302
+ ]
2303
+ }
2304
+ ) })
2305
+ ] });
2306
+ };
2288
2307
  var signatureOf = (sections) => JSON.stringify(sections, (k, v) => k === "age" ? void 0 : v);
2289
2308
  var App = ({
2290
2309
  fetcher,
@@ -2302,7 +2321,8 @@ var App = ({
2302
2321
  ciFetcher,
2303
2322
  ciPollMs = 6e4,
2304
2323
  extensions,
2305
- tabHelp
2324
+ tabHelp,
2325
+ emptyHint
2306
2326
  }) => {
2307
2327
  const [state, setState] = useState({ phase: "loading" });
2308
2328
  const [pending, setPending] = useState(null);
@@ -2334,8 +2354,8 @@ var App = ({
2334
2354
  const freshKey = signatureOf(fresh.sections);
2335
2355
  if (!displayedKey.current) {
2336
2356
  if (fresh.sections.length === 0) {
2337
- console.log(`${title[0].toUpperCase()}${title.slice(1)} empty.`);
2338
- process.exit(0);
2357
+ setState({ phase: "empty" });
2358
+ return;
2339
2359
  }
2340
2360
  showData(fresh.sections, fresh.login);
2341
2361
  } else if (freshKey !== displayedKey.current) {
@@ -2347,8 +2367,8 @@ var App = ({
2347
2367
  setRefreshing(false);
2348
2368
  const message = err.message;
2349
2369
  if (!displayedKey.current) {
2350
- console.error("Error:", message);
2351
- process.exit(1);
2370
+ setState({ phase: "failed", message });
2371
+ return;
2352
2372
  }
2353
2373
  setRefreshError({ message, at: Date.now() });
2354
2374
  });
@@ -2392,7 +2412,7 @@ var App = ({
2392
2412
  clearInterval(id);
2393
2413
  };
2394
2414
  }, [hasCiStatus, ciFetcher, ciPollMs]);
2395
- if (state.phase === "loading")
2415
+ if (state.phase === "loading" || state.phase === "empty" || state.phase === "failed")
2396
2416
  return /* @__PURE__ */ jsxs(
2397
2417
  Box,
2398
2418
  {
@@ -2409,12 +2429,20 @@ var App = ({
2409
2429
  brand: brandOf(title),
2410
2430
  sections: [],
2411
2431
  login: "",
2412
- work: workToggle ? initialIncludeWork ?? true : void 0,
2413
- loading: true
2432
+ work: workToggle && state.phase === "loading" ? initialIncludeWork ?? true : void 0,
2433
+ loading: state.phase === "loading",
2434
+ quiet: state.phase !== "loading"
2414
2435
  }
2415
2436
  ),
2416
2437
  hasCiStatus ? /* @__PURE__ */ jsx(CiStatusLine, { state: ciStatusState, job: ciJob }) : null,
2417
- /* @__PURE__ */ jsx(LoadingScreen, { label: `Fetching ${title}\u2026` })
2438
+ state.phase === "loading" ? /* @__PURE__ */ jsx(LoadingScreen, { label: `Fetching ${title}\u2026` }) : /* @__PURE__ */ jsx(
2439
+ NoRowsScreen,
2440
+ {
2441
+ reason: state.phase === "empty" ? "empty" : "failed",
2442
+ detail: state.phase === "failed" ? state.message : emptyHint,
2443
+ onRetry: () => revalidate(true)
2444
+ }
2445
+ )
2418
2446
  ]
2419
2447
  }
2420
2448
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",