@kud/gh-ink 0.52.1 → 0.53.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/index.d.ts CHANGED
@@ -70,6 +70,14 @@ type Sidebar = {
70
70
  /** Heading for the rail. The host's vocabulary, never ours. */
71
71
  title: string;
72
72
  rows: SidebarRow[];
73
+ /**
74
+ * What to call `live` on screen, in the host's words — `4 on board` and `off
75
+ * board` on a ticket cockpit, say. Takes the number rather than a word pair
76
+ * because the two states need not share a sentence shape: "off board" has no
77
+ * N in it. `live` itself stays a number, so the absent-draws-nothing contract
78
+ * and the host's sort are untouched.
79
+ */
80
+ liveLabel?: (live: number) => string;
73
81
  };
74
82
  /**
75
83
  * The rail's width, INCLUDING its rule and padding. Exported because the list
@@ -86,15 +94,13 @@ type Sidebar = {
86
94
  */
87
95
  declare const SIDEBAR_COLS = 52;
88
96
  /**
89
- * The dim figures after the key: how far through, and how much is moving.
90
- *
91
- * Either half may be missing and the other still worth saying, so this is a join
92
- * of what is known rather than one of three fixed shapes. `done`/`total` are
93
- * treated as a pair — a numerator with no denominator is not progress, it is a
94
- * number — and a zero is printed wherever it was counted, because "nothing is
95
- * moving" is exactly the thing a roadmap is read to notice.
97
+ * The plain-text facts of a row, for a host or a test that wants them as one
98
+ * string: how far through, then how much is moving. Either half may be missing
99
+ * and the other still worth saying, and a zero is printed wherever it was
100
+ * counted, because "nothing is moving" is exactly the thing a roadmap is read
101
+ * to notice.
96
102
  */
97
- declare const counts: (row: SidebarRow) => string;
103
+ declare const counts: (row: SidebarRow, liveLabel?: (live: number) => string) => string;
98
104
  /**
99
105
  * How many rows fit in `height` — one fewer than would physically fit whenever
100
106
  * that means anything is left over, to buy the line that says so.
@@ -108,10 +114,12 @@ declare const railCapacity: (height: number, rows: number) => number;
108
114
  /**
109
115
  * A right-hand rail of initiatives, standing beside the list.
110
116
  *
111
- * Two lines per row rather than one, because both halves are load-bearing and
112
- * neither survives the other being cut: a key with no words cannot be read at a
113
- * glance, and words with no key cannot be opened. Forty columns is not enough for
114
- * both on one line, so they stack.
117
+ * Label first, facts beneath, because a roadmap is read by name: nobody thinks
118
+ * "ACC-11312", they think "the OpenSearch migration". So the label is the
119
+ * bright line and the key drops to the facts line as what it is — a reference
120
+ * you follow, in the secondary tier. Two lines rather than one because both are
121
+ * load-bearing and neither survives the other being cut: words with no key
122
+ * cannot be opened, and a key with no words cannot be read at a glance.
115
123
  *
116
124
  * Presentational, like every other row renderer here: it DRAWS a cursor but does
117
125
  * not own one, and it never calls `useInput`. Where the cursor is, and whether
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React2, { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
2
  import { useWindowSize, useInput, Text, Box, useApp } from 'ink';
3
- import { colors, ScrollView, TextInput, LoadingScreen, StatusMessage, FooterHints, useListCursor, Tabs, pillWidth, Pill } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, LoadingScreen, ProgressBar, StatusMessage, FooterHints, useListCursor, Tabs, pillWidth, Pill } from '@kud/ink-ui';
4
4
  import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
  import { $ } from 'zx';
@@ -585,40 +585,75 @@ var HealthPanel = ({
585
585
  ] }) }) : note ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.success, children: " " + note }) }) : null
586
586
  ] });
587
587
  };
588
- var ACCENT = "#FF8700";
589
588
  var SIDEBAR_COLS = 52;
590
589
  var RULE = 1;
591
590
  var PAD = 2;
592
591
  var CONTENT = SIDEBAR_COLS - RULE - PAD;
593
- var LABEL_INDENT = 2;
594
- var RULE_COLOR = "gray";
595
- var counts = (row) => [
596
- row.done !== void 0 && row.total !== void 0 ? `${row.done}/${row.total}` : "",
597
- row.live !== void 0 ? `${row.live} live` : ""
598
- ].filter(Boolean).join(" \xB7 ");
599
- var truncate = (text, max) => [...text].length <= max ? text : [...text].slice(0, Math.max(0, max - 1)).join("") + "\u2026";
592
+ var MARKS = 4;
593
+ var LABEL_COLS = CONTENT - MARKS;
594
+ var FACTS_INDENT = 6;
595
+ var KEY_COLS = 10;
596
+ var BAR_COLS = 10;
597
+ var FRACTION_COLS = 5;
598
+ var GUTTER = " ";
599
+ var LIVE_COLS = CONTENT - FACTS_INDENT - KEY_COLS - BAR_COLS - FRACTION_COLS - GUTTER.length * 3;
600
+ var defaultLiveLabel = (live) => live === 0 ? "nothing live" : `${live} live`;
601
+ var fractionOf = (row) => row.done !== void 0 && row.total !== void 0 ? `${row.done}/${row.total}` : "";
602
+ var counts = (row, liveLabel = defaultLiveLabel) => [fractionOf(row), row.live !== void 0 ? liveLabel(row.live) : ""].filter(Boolean).join(" \xB7 ");
603
+ var DANGLING = /[\s:;,.\-—–(\[{]+$/u;
604
+ var truncateWords = (text, max) => {
605
+ const chars = [...text];
606
+ if (chars.length <= max) return text;
607
+ const room = Math.max(0, max - 1);
608
+ const hard = chars.slice(0, room).join("");
609
+ const boundary = chars[room] === " " ? room : hard.lastIndexOf(" ");
610
+ const soft = boundary >= Math.floor(room * 0.6) ? hard.slice(0, boundary) : hard;
611
+ return soft.replace(DANGLING, "") + "\u2026";
612
+ };
600
613
  var ROW_LINES = 3;
601
614
  var HEADING_LINES = 2;
602
615
  var railCapacity = (height, rows) => {
603
616
  const fits = Math.max(0, Math.floor((height - HEADING_LINES) / ROW_LINES));
604
617
  return fits >= rows ? rows : Math.max(0, fits - 1);
605
618
  };
619
+ var percentOf = (done, total) => total === 0 ? 0 : Math.round(done / total * 100);
620
+ var Progress = ({ row }) => {
621
+ const fraction = fractionOf(row);
622
+ const bar = row.done !== void 0 && row.total !== void 0 && row.total > 0 ? /* @__PURE__ */ jsx(
623
+ ProgressBar,
624
+ {
625
+ value: percentOf(row.done, row.total),
626
+ width: BAR_COLS,
627
+ color: colors.secondary
628
+ }
629
+ ) : /* @__PURE__ */ jsx(Text, { children: " ".repeat(BAR_COLS) });
630
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
631
+ bar,
632
+ /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: GUTTER + fraction.padStart(FRACTION_COLS) })
633
+ ] });
634
+ };
606
635
  var SidePanel = ({
607
636
  sidebar,
608
637
  height,
609
638
  focused = false,
610
639
  cursor = 0
611
640
  }) => {
641
+ const liveLabel = sidebar.liveLabel ?? defaultLiveLabel;
612
642
  const capacity = height === void 0 ? sidebar.rows.length : railCapacity(height, sidebar.rows.length);
613
643
  const start = focused && cursor >= capacity ? Math.min(cursor - capacity + 1, sidebar.rows.length - capacity) : 0;
614
644
  const shown = sidebar.rows.slice(start, start + capacity);
615
645
  const hidden = sidebar.rows.length - shown.length;
646
+ const focus = focused ? " \u25CF focus" : "";
647
+ const rule = "\u254C".repeat(
648
+ Math.max(0, CONTENT - [...sidebar.title].length - focus.length - 1)
649
+ );
616
650
  return (
617
651
  // A rule down the left rather than a full box: the rail's other three edges
618
652
  // already have the frame's border a column or two away, and a second
619
653
  // rectangle inside the first reads as a nested panel — something you could
620
654
  // focus and act on, which this cannot be. One line is the whole claim: what
621
- // is left of it is the list, what is right of it is not.
655
+ // is left of it is the list, what is right of it is not. In the frame's own
656
+ // colour and not dimmed, so the two rules read as one hierarchy.
622
657
  /* @__PURE__ */ jsxs(
623
658
  Box,
624
659
  {
@@ -627,31 +662,33 @@ var SidePanel = ({
627
662
  flexShrink: 0,
628
663
  height,
629
664
  borderStyle: "single",
630
- borderColor: RULE_COLOR,
631
- borderDimColor: true,
665
+ borderColor: colors.muted,
632
666
  borderTop: false,
633
667
  borderRight: false,
634
668
  borderBottom: false,
635
669
  paddingLeft: PAD,
636
670
  children: [
637
671
  /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
638
- /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: "\xBB " }),
639
672
  /* @__PURE__ */ jsx(Text, { bold: true, children: sidebar.title }),
640
- focused ? /* @__PURE__ */ jsx(Text, { color: ACCENT, children: " \u25CF focus" }) : null
673
+ focused ? /* @__PURE__ */ jsx(Text, { color: colors.accent, children: focus }) : null,
674
+ /* @__PURE__ */ jsx(Text, { color: colors.info, dimColor: true, children: ` ${rule}` })
641
675
  ] }),
642
- sidebar.rows.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: LABEL_INDENT, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "nothing open" }) }) : shown.map((row, i) => {
676
+ sidebar.rows.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: "nothing open" }) }) : shown.map((row, i) => {
643
677
  const active = focused && start + i === cursor;
644
678
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
645
679
  /* @__PURE__ */ jsxs(Box, { children: [
646
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: active ? "\u276F " : " " }),
647
- /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
648
- /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: active, children: row.key }),
649
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${counts(row)}` })
680
+ /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? "\u276F " : " " }),
681
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: row.wantsYou ? "\u2190 " : " " }),
682
+ /* @__PURE__ */ jsx(Text, { bold: active, children: truncateWords(row.label, LABEL_COLS) })
650
683
  ] }),
651
- /* @__PURE__ */ jsx(Box, { paddingLeft: LABEL_INDENT + 2, children: /* @__PURE__ */ jsx(Text, { dimColor: !active, children: truncate(row.label, CONTENT - LABEL_INDENT - 2) }) })
684
+ /* @__PURE__ */ jsxs(Box, { paddingLeft: FACTS_INDENT, children: [
685
+ /* @__PURE__ */ jsx(Text, { color: colors.secondary, children: row.key.padEnd(KEY_COLS) + GUTTER }),
686
+ /* @__PURE__ */ jsx(Progress, { row }),
687
+ /* @__PURE__ */ jsx(Text, { children: GUTTER + (row.live !== void 0 ? liveLabel(row.live).slice(0, LIVE_COLS) : "") })
688
+ ] })
652
689
  ] }, row.key);
653
690
  }),
654
- hidden > 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: LABEL_INDENT, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: `+${hidden} more` }) }) : null
691
+ hidden > 0 ? /* @__PURE__ */ jsx(Box, { paddingLeft: MARKS, children: /* @__PURE__ */ jsx(Text, { color: colors.muted, children: `+${hidden} more` }) }) : null
655
692
  ]
656
693
  }
657
694
  )
@@ -1135,7 +1172,7 @@ var withHeaders = (items, idx) => {
1135
1172
  start--;
1136
1173
  return start;
1137
1174
  };
1138
- var truncate2 = (str, max) => {
1175
+ var truncate = (str, max) => {
1139
1176
  if (str.length <= max) return str;
1140
1177
  const half = Math.floor((max - 1) / 2);
1141
1178
  return `${str.slice(0, half)}\u2026${str.slice(-half)}`;
@@ -1723,18 +1760,18 @@ var InboxHeader = ({
1723
1760
  const userSeg = loading || quiet ? "" : `@${login} `;
1724
1761
  const workLabel = scopeLabel ? ` ${scopeLabel} ` : "";
1725
1762
  const budgetSeg = budgetLabel ? budgetLabel + " " : "";
1726
- const [statusText, statusColor] = hasPending ? [`\u25CF ${pendingSummary || "new"} \xB7 r apply`, "#FF8700"] : refreshing ? ["\u21BB refreshing\u2026", "cyan"] : appliedSummary ? [`\u25C9 ${appliedSummary}`, "#FF8700"] : fetchedAt ? [`updated ${agoText(fetchedAt)}`, void 0] : ["", void 0];
1763
+ const [statusText, statusColor] = hasPending ? [`\u25CF ${pendingSummary || "new"} \xB7 r apply`, colors.accent] : refreshing ? ["\u21BB refreshing\u2026", "cyan"] : appliedSummary ? [`\u25C9 ${appliedSummary}`, colors.accent] : fetchedAt ? [`updated ${agoText(fetchedAt)}`, void 0] : ["", void 0];
1727
1764
  const statusSeg = statusText ? statusText + " " : "";
1728
1765
  const fill = Math.max(
1729
1766
  4,
1730
1767
  COLS - brand.length - countSeg.length - userSeg.length - workLabel.length - budgetSeg.length - statusSeg.length
1731
1768
  );
1732
1769
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1733
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: brand }),
1770
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: brand }),
1734
1771
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: countSeg }),
1735
1772
  userSeg ? /* @__PURE__ */ jsx(Text3, { children: userSeg }) : null,
1736
1773
  scopeLabel ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: workLabel }) : null,
1737
- budgetLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: budgetCritical ? "#FF5F5F" : "#FF8700", children: budgetSeg }) : null,
1774
+ budgetLabel ? /* @__PURE__ */ jsx(Text3, { bold: true, color: budgetCritical ? "#FF5F5F" : colors.accent, children: budgetSeg }) : null,
1738
1775
  statusText ? /* @__PURE__ */ jsx(
1739
1776
  Text3,
1740
1777
  {
@@ -1783,7 +1820,7 @@ var CiStatusLine = ({
1783
1820
  /* @__PURE__ */ jsx(Text3, { color, bold: true, children: "\u25CF " }),
1784
1821
  /* @__PURE__ */ jsx(Text3, { bold: true, children: status.job }),
1785
1822
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + label }),
1786
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: " #" + status.buildNumber }),
1823
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " #" + status.buildNumber }),
1787
1824
  status.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + status.age }) : null
1788
1825
  ] });
1789
1826
  };
@@ -1825,13 +1862,13 @@ var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
1825
1862
  var TRANSIT_COLOUR = {
1826
1863
  in: "#3FB950",
1827
1864
  out: "#8B949E",
1828
- changed: "#FF8700",
1865
+ changed: colors.accent,
1829
1866
  // A move is neither an arrival nor a departure, so it takes the colour of the
1830
1867
  // third thing that can happen to a row you already had: it changed. Both ends
1831
1868
  // of one move wear the same colour and the same word, which is what lets you
1832
1869
  // recognise the row you just watched leave when you land on the tab it went to.
1833
- "moved-in": "#FF8700",
1834
- "moved-out": "#FF8700"
1870
+ "moved-in": colors.accent,
1871
+ "moved-out": colors.accent
1835
1872
  };
1836
1873
  var TRANSIT_LABEL = {
1837
1874
  out: "gone"};
@@ -1863,7 +1900,7 @@ var ItemRow = ({
1863
1900
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
1864
1901
  if (item.kind === "subgroup-header")
1865
1902
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1866
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: " \xBB " }),
1903
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: " \xBB " }),
1867
1904
  /* @__PURE__ */ jsx(Text3, { bold: true, children: item.label })
1868
1905
  ] });
1869
1906
  if (item.kind === "show-more")
@@ -1892,14 +1929,14 @@ var ItemRow = ({
1892
1929
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1893
1930
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
1894
1931
  /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1895
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: active, children: item.key + " " }),
1932
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: active, children: item.key + " " }),
1896
1933
  /* @__PURE__ */ jsx(
1897
1934
  Text3,
1898
1935
  {
1899
1936
  bold: active || !!transient && isArrival(transient),
1900
1937
  dimColor: !!transient && isDeparture(transient),
1901
1938
  strikethrough: transient === "out",
1902
- children: truncate2(item.summary, titleMax2)
1939
+ children: truncate(item.summary, titleMax2)
1903
1940
  }
1904
1941
  ),
1905
1942
  item.pill && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -1925,7 +1962,7 @@ var ItemRow = ({
1925
1962
  const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient && isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : transient && isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : healthIcon;
1926
1963
  const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
1927
1964
  const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
1928
- const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, "#FF8700"] : !login || !item.lastActor ? [" ", "white"] : spokeLast ? ["\u2192", colors.secondary] : ["\u2190", "#FF8700"];
1965
+ const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor ? [" ", "white"] : spokeLast ? ["\u2192", colors.secondary] : ["\u2190", colors.accent];
1929
1966
  const numStr = `#${item.number}`.padEnd(7);
1930
1967
  const showAuthor = !!item.author && item.author !== login;
1931
1968
  const sizeLabel = sizeOf(item);
@@ -1958,7 +1995,7 @@ var ItemRow = ({
1958
1995
  if (next.length <= LABEL_CELL_MAX) fitted.push(name);
1959
1996
  }
1960
1997
  if (fitted.length === 0) {
1961
- return `\uF02B ${truncate2(shown[0], LABEL_CELL_MAX)}`;
1998
+ return `\uF02B ${truncate(shown[0], LABEL_CELL_MAX)}`;
1962
1999
  }
1963
2000
  return `\uF02B ${fitted.join(", ")}`;
1964
2001
  };
@@ -1994,14 +2031,14 @@ var ItemRow = ({
1994
2031
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1995
2032
  /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
1996
2033
  /* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1997
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: numStr }),
2034
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: numStr }),
1998
2035
  /* @__PURE__ */ jsx(
1999
2036
  Text3,
2000
2037
  {
2001
2038
  bold: active || !!transient && isArrival(transient),
2002
2039
  dimColor: !!transient && isDeparture(transient),
2003
2040
  strikethrough: transient === "out",
2004
- children: truncate2(item.title, titleMax) + " "
2041
+ children: truncate(item.title, titleMax) + " "
2005
2042
  }
2006
2043
  ),
2007
2044
  labelLabel ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: labelLabel + " " }) : null,
@@ -2012,7 +2049,7 @@ var ItemRow = ({
2012
2049
  " ",
2013
2050
  /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
2014
2051
  ] }) : null,
2015
- unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(Text3, { bold: !spokeLast, color: spokeLast ? colors.secondary : "#FF8700", children: " " + unresolvedLabel }) : null,
2052
+ unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(Text3, { bold: !spokeLast, color: spokeLast ? colors.secondary : colors.accent, children: " " + unresolvedLabel }) : null,
2016
2053
  showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2017
2054
  ageLabel && !givingUp.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + ageLabel }) : null,
2018
2055
  mergedLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2082,7 +2119,7 @@ var ActionMenu = ({
2082
2119
  );
2083
2120
  };
2084
2121
  var TURN_LEGEND = [
2085
- ["\u2190", "#FF8700", "They spoke last \xB7 your turn"],
2122
+ ["\u2190", colors.accent, "They spoke last \xB7 your turn"],
2086
2123
  ["\u2192", colors.secondary, "You spoke last \xB7 waiting on them"]
2087
2124
  ];
2088
2125
  var CELL_GUTTER = 2;
@@ -2187,7 +2224,7 @@ var HelpModal = ({
2187
2224
  })),
2188
2225
  {
2189
2226
  cell: "\uF086",
2190
- color: "#FF8700",
2227
+ color: colors.accent,
2191
2228
  bold: true,
2192
2229
  label: "Open-thread count"
2193
2230
  }
@@ -2200,7 +2237,7 @@ var HelpModal = ({
2200
2237
  title: "Tabs",
2201
2238
  rows: tabHelp.map(([tab, meaning]) => ({
2202
2239
  cell: tab,
2203
- color: "#FF8700",
2240
+ color: colors.accent,
2204
2241
  label: meaning
2205
2242
  }))
2206
2243
  }
@@ -2275,7 +2312,7 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2275
2312
  paddingX: 1,
2276
2313
  width: Math.min(COLS, 78),
2277
2314
  children: [
2278
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2315
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, bold: true, children: `#${item.number} \xB7 ${item.repo}` }),
2279
2316
  /* @__PURE__ */ jsx(Text3, { children: item.title }),
2280
2317
  explainItem(item, login).map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2281
2318
  /* @__PURE__ */ jsx(Text3, { bold: true, dimColor: true, children: section.heading }),
@@ -2577,11 +2614,11 @@ var BrowseScreen = ({
2577
2614
  if (key.ctrl || key.meta) return;
2578
2615
  if (help) {
2579
2616
  if (helpRange.max > 0) {
2580
- const step = (n) => setHelpScroll((s) => Math.min(helpRange.max, Math.max(0, s + n)));
2581
- if (key.upArrow) return step(-1);
2582
- if (key.downArrow) return step(1);
2583
- if (key.pageUp) return step(-helpRange.page);
2584
- if (key.pageDown) return step(helpRange.page);
2617
+ const step2 = (n) => setHelpScroll((s) => Math.min(helpRange.max, Math.max(0, s + n)));
2618
+ if (key.upArrow) return step2(-1);
2619
+ if (key.downArrow) return step2(1);
2620
+ if (key.pageUp) return step2(-helpRange.page);
2621
+ if (key.pageDown) return step2(helpRange.page);
2585
2622
  }
2586
2623
  setHelp(false);
2587
2624
  return;
@@ -2691,12 +2728,10 @@ var BrowseScreen = ({
2691
2728
  setCursors((p) => ({ ...p, [activeId]: next }));
2692
2729
  setViewStarts((p) => ({ ...p, [activeId]: newVs }));
2693
2730
  }
2694
- if (key.leftArrow) setTabIdx((i) => Math.max(0, i - 1));
2695
- if (key.rightArrow)
2696
- setTabIdx((i) => Math.min(localSections.length - 1, i + 1));
2697
- if (key.tab)
2731
+ const step = key.leftArrow || key.tab && key.shift ? -1 : key.rightArrow || key.tab ? 1 : 0;
2732
+ if (step !== 0)
2698
2733
  setTabIdx(
2699
- (i) => (i + (key.shift ? -1 : 1) + localSections.length) % localSections.length
2734
+ (i) => (i + step + localSections.length) % localSections.length
2700
2735
  );
2701
2736
  if (input === "q") exit();
2702
2737
  if (input === "r") {
@@ -3011,7 +3046,7 @@ var BrowseScreen = ({
3011
3046
  ),
3012
3047
  ciStatusState ? /* @__PURE__ */ jsx(CiStatusLine, { state: ciStatusState, job: ciJob }) : null,
3013
3048
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3014
- /* @__PURE__ */ jsx(Text3, { bold: true, color: "#FF8700", children: " \u26A1 auto-refresh paused to save API budget" }),
3049
+ /* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
3015
3050
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
3016
3051
  ] }) : null,
3017
3052
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(
@@ -3043,7 +3078,7 @@ var BrowseScreen = ({
3043
3078
  s.id,
3044
3079
  pulseSettled ? PULSE_SETTLED_FRAME : sparkFrame
3045
3080
  ),
3046
- markerColor: "#FF8700"
3081
+ markerColor: colors.accent
3047
3082
  }))
3048
3083
  }
3049
3084
  ) }),
@@ -3053,7 +3088,7 @@ var BrowseScreen = ({
3053
3088
  searchInput ? /* @__PURE__ */ jsx(Text3, { color: "cyan", children: "\u258F" }) : null,
3054
3089
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3055
3090
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3056
- /* @__PURE__ */ jsx(Text3, { color: "#FF8700", children: " \u25C9 " }),
3091
+ /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
3057
3092
  /* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
3058
3093
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " f edit \xB7 esc clear" })
3059
3094
  ] }) : null,
@@ -3596,4 +3631,4 @@ var matchesFilter = (repo, filter) => {
3596
3631
  };
3597
3632
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3598
3633
 
3599
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate2 as truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
3634
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.52.1",
3
+ "version": "0.53.0",
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",
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@kud/gh": "0.16.0",
52
52
  "@kud/gh-workflow": "0.11.0",
53
- "@kud/ink-ui": "0.28.2",
53
+ "@kud/ink-ui": "0.29.0",
54
54
  "zx": "8.8.5"
55
55
  },
56
56
  "devDependencies": {