@kud/gh-ink 0.56.0 → 0.57.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
@@ -248,6 +248,22 @@ type DetailContext = {
248
248
  * once. Optional so a host that cannot merge need not implement it.
249
249
  */
250
250
  onMerged?: (item: GHItem) => void;
251
+ /**
252
+ * Where a drill publishes its PEEL — close the topmost layer it owns and say
253
+ * whether there was one. `esc` and `backspace` are bound once at the app root
254
+ * and routed here; a drill view binds neither itself, per `@kud/ink-ui`'s
255
+ * contract that a view exported from a `*-ink` package takes `onBack` and
256
+ * leaves the app's keys to the app.
257
+ *
258
+ * Returning `false` means "no layer of mine was open", at which point the root
259
+ * closes the drill itself.
260
+ */
261
+ registerPeel?: (peel: (() => boolean) | null) => void;
262
+ /**
263
+ * True while a text field inside the drill owns the keyboard — a reply box —
264
+ * so the root stands `q`, `esc` and `backspace` down and the field keeps them.
265
+ */
266
+ onTyping?: (typing: boolean) => void;
251
267
  };
252
268
  type Action = {
253
269
  label: string;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React2, { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
- import { useWindowSize, useInput, Text, Box, useApp } from 'ink';
3
- import { colors, ScrollView, TextInput, LoadingScreen, ProgressBar, StatusMessage, FooterHints, useListCursor, Tabs, pillWidth, Pill } from '@kud/ink-ui';
2
+ import { useWindowSize, useInput, Text, Box } from 'ink';
3
+ import { colors, ScrollView, TextInput, useAppKeys, 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';
@@ -8,7 +8,7 @@ import { spawn } from 'child_process';
8
8
  import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, watch, rmSync } from 'fs';
9
9
  import { join, dirname, basename } from 'path';
10
10
  import { homedir } from 'os';
11
- import { depthOf, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, PIN_MARK, sizeOf, sizePartsOf, impliedLabels, labelPriority } from '@kud/gh-workflow';
11
+ import { PIN_MARK, depthOf, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, sizeOf, sizePartsOf, impliedLabels, labelPriority } from '@kud/gh-workflow';
12
12
  export { PIN_MARK, bandOf, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, impliedLabels, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
13
13
 
14
14
  // src/components/comments-panel.tsx
@@ -1050,7 +1050,7 @@ var checksSentence = (d) => {
1050
1050
  var turnSentences = (item, login) => {
1051
1051
  if (item.pinned)
1052
1052
  return [
1053
- "You pinned this (!). It stays under Your move until you unpin it, whatever else the row says."
1053
+ "You pinned this (+). It stays under Your move until you unpin it, whatever else the row says."
1054
1054
  ];
1055
1055
  if (!item.lastActor) return ["Nothing has been said on it yet."];
1056
1056
  const d = item.detail;
@@ -1060,9 +1060,9 @@ var turnSentences = (item, login) => {
1060
1060
  const them = item.author && item.author !== login ? item.author : null;
1061
1061
  if (!them)
1062
1062
  return [
1063
- `You spoke last (\u2192), ${when}. It is waiting on a reviewer, not on you.`
1063
+ `You spoke last, ${when}. It is waiting on a reviewer, not on you.`
1064
1064
  ];
1065
- const lines = [`You spoke last (\u2192), ${when}. The ball is with ${them}.`];
1065
+ const lines = [`You spoke last, ${when}. The ball is with ${them}.`];
1066
1066
  if (d?.lastCommitAt && d.lastEventAt && d.lastCommitAt < d.lastEventAt)
1067
1067
  lines.push(
1068
1068
  `Nothing has been pushed since ${relativeTime(
@@ -1849,7 +1849,7 @@ var InboxHeader = ({
1849
1849
  children: statusSeg
1850
1850
  }
1851
1851
  ) : null,
1852
- /* @__PURE__ */ jsx(Text3, { color: "cyan", dimColor: true, children: "\u254C".repeat(fill) })
1852
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, dimColor: true, children: "\u254C".repeat(fill) })
1853
1853
  ] });
1854
1854
  };
1855
1855
  var toCiStatusState = (status) => status ? { kind: "ready", status } : { kind: "error" };
@@ -1877,7 +1877,7 @@ var CiStatusLine = ({
1877
1877
  ] });
1878
1878
  if (state.kind === "error")
1879
1879
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1880
- /* @__PURE__ */ jsx(Text3, { color: "red", bold: true, children: " \u2717 " }),
1880
+ /* @__PURE__ */ jsx(Text3, { color: colors.error, bold: true, children: " \u2717 " }),
1881
1881
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `${name} no build / not configured` })
1882
1882
  ] });
1883
1883
  const { status } = state;
@@ -1902,9 +1902,17 @@ var RepoHeaderRow = ({
1902
1902
  const fill = Math.max(4, FENCE_COLS - label.length);
1903
1903
  const rule = "\u2500".repeat(fill);
1904
1904
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1905
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1905
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1906
1906
  /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: "\u2500\u2500 " }),
1907
- /* @__PURE__ */ jsx(Text3, { dimColor: !active, bold: active, children: repo }),
1907
+ /* @__PURE__ */ jsx(
1908
+ Text3,
1909
+ {
1910
+ dimColor: false,
1911
+ color: active ? void 0 : colors.secondary,
1912
+ bold: active,
1913
+ children: repo
1914
+ }
1915
+ ),
1908
1916
  /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
1909
1917
  ] });
1910
1918
  };
@@ -1958,7 +1966,8 @@ var ItemRow = ({
1958
1966
  parent,
1959
1967
  sparkFrame = 0,
1960
1968
  cols = COLS,
1961
- markerCols = 0
1969
+ markerCols = 0,
1970
+ uniformLabels = []
1962
1971
  }) => {
1963
1972
  const backdropped = useBackdropped();
1964
1973
  const transient = leaving ? "out" : refreshMark;
@@ -1974,14 +1983,14 @@ var ItemRow = ({
1974
1983
  ] });
1975
1984
  if (item.kind === "show-more")
1976
1985
  return /* @__PURE__ */ jsxs(Box, { children: [
1977
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1986
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1978
1987
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1979
1988
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1980
1989
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: `+${item.hidden.length} more` })
1981
1990
  ] });
1982
1991
  if (item.kind === "show-less")
1983
1992
  return /* @__PURE__ */ jsxs(Box, { children: [
1984
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
1993
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1985
1994
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1986
1995
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: active ? "\u21B5 " : " " }),
1987
1996
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "show less" })
@@ -1994,7 +2003,7 @@ var ItemRow = ({
1994
2003
  cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - (markerCols > 0 ? markerCols + 1 : 0) - 12
1995
2004
  );
1996
2005
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1997
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
2006
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
1998
2007
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
1999
2008
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
2000
2009
  /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
@@ -2032,17 +2041,18 @@ var ItemRow = ({
2032
2041
  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;
2033
2042
  const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
2034
2043
  const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
2035
- const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor ? [" ", "white"] : spokeLast ? ["\u2192", colors.secondary] : ["\u2190", colors.accent];
2044
+ const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
2036
2045
  const numStr = `#${item.number}`.padEnd(7);
2037
2046
  const showAuthor = !!item.author && item.author !== login;
2038
2047
  const sizeLabel = sizeOf(item);
2039
2048
  const sizeParts = sizePartsOf(item);
2040
2049
  const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
2041
- const ageLabel = item.activityAge && item.activityAge !== item.age ? `${item.activityAge} \xB7 ${item.age}` : item.age;
2050
+ const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
2051
+ const ageLabel = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
2042
2052
  const mergedLabel = merged ? "merged" : "";
2043
2053
  const pillCaps = [mergedLabel, farewellLabel].filter(Boolean).length * 2;
2044
2054
  const repoLabel = depthOf(item) > 0 ? item.repo : "";
2045
- const implied = impliedLabels(item.repo);
2055
+ const implied = [...impliedLabels(item.repo), ...uniformLabels];
2046
2056
  const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
2047
2057
  const givingUp = {
2048
2058
  author: false,
@@ -2097,7 +2107,7 @@ var ItemRow = ({
2097
2107
  const labelLabel = labelCell();
2098
2108
  const titleMax = Math.max(1, cols - widthOf2());
2099
2109
  return /* @__PURE__ */ jsxs(Box, { children: [
2100
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
2110
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2101
2111
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
2102
2112
  /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
2103
2113
  /* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
@@ -2119,9 +2129,19 @@ var ItemRow = ({
2119
2129
  " ",
2120
2130
  /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
2121
2131
  ] }) : null,
2122
- unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(Text3, { bold: !spokeLast, color: spokeLast ? colors.secondary : colors.accent, children: " " + unresolvedLabel }) : null,
2132
+ unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
2133
+ Text3,
2134
+ {
2135
+ bold: !spokeLast,
2136
+ color: spokeLast ? colors.secondary : colors.accent,
2137
+ children: " " + unresolvedLabel
2138
+ }
2139
+ ) : null,
2123
2140
  showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
2124
- ageLabel && !givingUp.age ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + ageLabel }) : null,
2141
+ ageLabel && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
2142
+ /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + agePair.activity }),
2143
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` (${agePair.lifetime})` })
2144
+ ] }) : /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + ageLabel }) : null,
2125
2145
  mergedLabel && !backdropped ? /* @__PURE__ */ jsxs(Fragment, { children: [
2126
2146
  /* @__PURE__ */ jsx(Text3, { children: " " }),
2127
2147
  /* @__PURE__ */ jsx(Pill, { color: MERGED_COLOUR, children: mergedLabel })
@@ -2170,15 +2190,15 @@ var ActionMenu = ({
2170
2190
  {
2171
2191
  flexDirection: "column",
2172
2192
  borderStyle: "round",
2173
- borderColor: "cyan",
2193
+ borderColor: colors.info,
2174
2194
  backgroundColor: OVERLAY_BG,
2175
2195
  paddingX: 1,
2176
2196
  marginTop: 1,
2177
2197
  children: [
2178
- /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: title }),
2198
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: title }),
2179
2199
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(32) }),
2180
2200
  actions.map((a, i) => /* @__PURE__ */ jsxs(Box, { children: [
2181
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: i === cursor ? "\u276F " : " " }),
2201
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: i === cursor ? "\u276F " : " " }),
2182
2202
  /* @__PURE__ */ jsx(Text3, { bold: i === cursor, children: a.label }),
2183
2203
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: " " + a.hint })
2184
2204
  ] }, a.label)),
@@ -2189,8 +2209,8 @@ var ActionMenu = ({
2189
2209
  );
2190
2210
  };
2191
2211
  var TURN_LEGEND = [
2192
- ["\u2190", colors.accent, "They spoke last \xB7 your turn"],
2193
- ["\u2192", colors.secondary, "You spoke last \xB7 waiting on them"]
2212
+ ["\u2190", colors.accent, "They spoke last \xB7 reply owed"],
2213
+ [PIN_MARK, colors.accent, "Pinned \xB7 stays under Your move"]
2194
2214
  ];
2195
2215
  var CELL_GUTTER = 2;
2196
2216
  var COL_GAP = 3;
@@ -2347,13 +2367,13 @@ var HelpModal = ({
2347
2367
  {
2348
2368
  flexDirection: "column",
2349
2369
  borderStyle: "round",
2350
- borderColor: "cyan",
2370
+ borderColor: colors.info,
2351
2371
  backgroundColor: OVERLAY_BG,
2352
2372
  paddingX: 1,
2353
2373
  width: contentWidth + MODAL_CHROME_COLS,
2354
2374
  children: [
2355
2375
  /* @__PURE__ */ jsxs(Box, { children: [
2356
- /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: "Legend" }),
2376
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: "Legend" }),
2357
2377
  scrollable ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
2358
2378
  ] }),
2359
2379
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
@@ -2377,7 +2397,7 @@ var ExplainModal = ({ item, login }) => /* @__PURE__ */ jsxs(
2377
2397
  {
2378
2398
  flexDirection: "column",
2379
2399
  borderStyle: "round",
2380
- borderColor: "cyan",
2400
+ borderColor: colors.info,
2381
2401
  backgroundColor: OVERLAY_BG,
2382
2402
  paddingX: 1,
2383
2403
  width: Math.min(COLS, 78),
@@ -2409,19 +2429,19 @@ var RepoPicker = ({
2409
2429
  {
2410
2430
  flexDirection: "column",
2411
2431
  borderStyle: "round",
2412
- borderColor: "cyan",
2432
+ borderColor: colors.info,
2413
2433
  backgroundColor: OVERLAY_BG,
2414
2434
  paddingX: 1,
2415
2435
  minWidth: 42,
2416
2436
  children: [
2417
- /* @__PURE__ */ jsx(Text3, { color: "cyan", bold: true, children: `Filter by repo (${selected.size} on)` }),
2437
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, bold: true, children: `Filter by repo (${selected.size} on)` }),
2418
2438
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: "\u2500".repeat(36) }),
2419
2439
  visible.map((repo, i) => {
2420
2440
  const idx = start + i;
2421
2441
  const on = selected.has(repo);
2422
2442
  const active = idx === cursor;
2423
2443
  return /* @__PURE__ */ jsxs(Box, { children: [
2424
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: active ? "\u276F " : " " }),
2444
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
2425
2445
  /* @__PURE__ */ jsx(Text3, { color: on ? "green" : void 0, children: on ? "\u25C9 " : "\u25CB " }),
2426
2446
  /* @__PURE__ */ jsx(Text3, { bold: active, children: repo })
2427
2447
  ] }, repo);
@@ -2454,6 +2474,8 @@ var BrowseScreen = ({
2454
2474
  fetchedAt,
2455
2475
  refreshError,
2456
2476
  hidden,
2477
+ peelRef,
2478
+ onTyping,
2457
2479
  onOpenPr,
2458
2480
  onOpenIssue,
2459
2481
  onOpenExt,
@@ -2475,7 +2497,6 @@ var BrowseScreen = ({
2475
2497
  liveLabel
2476
2498
  }) => {
2477
2499
  const { rows } = useWindowSize();
2478
- const { exit } = useApp();
2479
2500
  const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match, login) : secs;
2480
2501
  const initialSections = applyWork(sections);
2481
2502
  const [localSections, setLocalSections] = useState(initialSections);
@@ -2579,6 +2600,12 @@ var BrowseScreen = ({
2579
2600
  (w, i) => i.kind === "task" ? Math.max(w, [...i.marker ?? ""].length) : w,
2580
2601
  0
2581
2602
  );
2603
+ const labelBearing = section.items.filter(
2604
+ (i) => i.kind === "pr" || i.kind === "issue"
2605
+ );
2606
+ const uniformLabels = labelBearing.length > 1 ? (labelBearing[0]?.labels ?? []).filter(
2607
+ (name) => labelBearing.every((i) => (i.labels ?? []).includes(name))
2608
+ ) : [];
2582
2609
  const hasMore = viewStart + visibleCount < section.items.length;
2583
2610
  const activeItem = section.items[cursor];
2584
2611
  const showFlash = (msg) => {
@@ -2600,6 +2627,26 @@ var BrowseScreen = ({
2600
2627
  const railRows = sidebar?.rows ?? [];
2601
2628
  const railActive = showRail && railFocus && railRows.length > 0;
2602
2629
  const railAt = Math.min(railCursor, Math.max(0, railRows.length - 1));
2630
+ const peel = () => {
2631
+ if (help) return setHelp(false), true;
2632
+ if (explain) return setExplain(false), true;
2633
+ if (repoPicker) return setRepoPicker(false), true;
2634
+ if (railActive) return setRailFocus(false), true;
2635
+ if (menu.actions !== null) return menu.close(), true;
2636
+ if (search != null) return setSearch(null), true;
2637
+ if (repoFilter.size > 0) return setRepoFilter(/* @__PURE__ */ new Set()), true;
2638
+ return false;
2639
+ };
2640
+ useEffect(() => {
2641
+ if (!peelRef) return;
2642
+ peelRef.current = peel;
2643
+ return () => {
2644
+ peelRef.current = null;
2645
+ };
2646
+ });
2647
+ useEffect(() => {
2648
+ onTyping?.(searchInput);
2649
+ }, [searchInput, onTyping]);
2603
2650
  const railCols = railWidth(COLS);
2604
2651
  const listCols = showRail ? COLS - railCols : COLS;
2605
2652
  const [sparkFrame, setSparkFrame] = useState(0);
@@ -2709,7 +2756,7 @@ var BrowseScreen = ({
2709
2756
  return;
2710
2757
  }
2711
2758
  if (repoPicker) {
2712
- if (key.escape || key.return) return setRepoPicker(false);
2759
+ if (key.return) return setRepoPicker(false);
2713
2760
  if (input === "a") return setRepoFilter(/* @__PURE__ */ new Set());
2714
2761
  if (input === " ") {
2715
2762
  const repo = allRepos[repoCursor];
@@ -2765,10 +2812,6 @@ var BrowseScreen = ({
2765
2812
  setRailCursor((c) => Math.min(railRows.length - 1, c + 1));
2766
2813
  return;
2767
2814
  }
2768
- if (key.escape) {
2769
- setRailFocus(false);
2770
- return;
2771
- }
2772
2815
  if (key.return || input === "o") {
2773
2816
  const row = railRows[railAt];
2774
2817
  if (!row) return;
@@ -2793,14 +2836,6 @@ var BrowseScreen = ({
2793
2836
  if (input !== "r" && input !== "q" && !key.leftArrow && !key.rightArrow)
2794
2837
  return;
2795
2838
  }
2796
- if (key.escape && search != null) {
2797
- setSearch(null);
2798
- return;
2799
- }
2800
- if (key.escape && repoFilter.size > 0) {
2801
- setRepoFilter(/* @__PURE__ */ new Set());
2802
- return;
2803
- }
2804
2839
  if (menu.handleKey(key)) return;
2805
2840
  if (key.upArrow) {
2806
2841
  const next = moveCursor(section.items, cursor, -1);
@@ -2818,10 +2853,7 @@ var BrowseScreen = ({
2818
2853
  }
2819
2854
  const step = key.leftArrow || key.tab && key.shift ? -1 : key.rightArrow || key.tab ? 1 : 0;
2820
2855
  if (step !== 0)
2821
- setTabIdx(
2822
- (i) => (i + step + localSections.length) % localSections.length
2823
- );
2824
- if (input === "q") exit();
2856
+ setTabIdx((i) => (i + step + localSections.length) % localSections.length);
2825
2857
  if (input === "r") {
2826
2858
  onRefresh?.();
2827
2859
  return;
@@ -3172,9 +3204,9 @@ var BrowseScreen = ({
3172
3204
  }
3173
3205
  ) }),
3174
3206
  search != null ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3175
- /* @__PURE__ */ jsx(Text3, { color: "cyan", children: " / " }),
3207
+ /* @__PURE__ */ jsx(Text3, { color: colors.info, children: " / " }),
3176
3208
  /* @__PURE__ */ jsx(Text3, { children: search }),
3177
- searchInput ? /* @__PURE__ */ jsx(Text3, { color: "cyan", children: "\u258F" }) : null,
3209
+ searchInput ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
3178
3210
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3179
3211
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3180
3212
  /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
@@ -3212,6 +3244,7 @@ var BrowseScreen = ({
3212
3244
  sparkFrame,
3213
3245
  cols: listCols,
3214
3246
  markerCols,
3247
+ uniformLabels,
3215
3248
  gap: i > 0 && gapsAbove(section.items, viewStart + i)
3216
3249
  },
3217
3250
  `${viewStart + i}:${rowKey(item)}`
@@ -3250,7 +3283,7 @@ var BrowseScreen = ({
3250
3283
  }
3251
3284
  ) : null
3252
3285
  ] }),
3253
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: "green", children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3286
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: flash ? /* @__PURE__ */ jsx(Text3, { color: colors.success, children: flash }) : /* @__PURE__ */ jsx(FooterHints, { hints }) })
3254
3287
  ]
3255
3288
  }
3256
3289
  );
@@ -3260,9 +3293,7 @@ var NoRowsScreen = ({
3260
3293
  detail,
3261
3294
  onRetry
3262
3295
  }) => {
3263
- const { exit } = useApp();
3264
3296
  useInput((input) => {
3265
- if (input === "q") exit();
3266
3297
  if (input === "r") onRetry();
3267
3298
  });
3268
3299
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -3311,6 +3342,24 @@ var App = ({
3311
3342
  }) => {
3312
3343
  const { rows } = useWindowSize();
3313
3344
  const [state, setState] = useState({ phase: "loading" });
3345
+ const browsePeel = useRef(null);
3346
+ const drillPeel = useRef(null);
3347
+ const [typing, setTyping] = useState(false);
3348
+ useAppKeys({
3349
+ isActive: !typing,
3350
+ onBack: () => {
3351
+ if (state.phase === "pr" || state.phase === "issue" || state.phase === "ext") {
3352
+ if (drillPeel.current?.()) return true;
3353
+ setState({
3354
+ phase: "browse",
3355
+ sections: state.sections,
3356
+ login: state.login
3357
+ });
3358
+ return true;
3359
+ }
3360
+ return browsePeel.current?.() ?? false;
3361
+ }
3362
+ });
3314
3363
  const [pending, setPending] = useState(null);
3315
3364
  const [refreshing, setRefreshing] = useState(false);
3316
3365
  const [fetchedAt, setFetchedAt] = useState(null);
@@ -3675,6 +3724,8 @@ var App = ({
3675
3724
  budget,
3676
3725
  skippedForBudget,
3677
3726
  hidden: overlay !== null,
3727
+ peelRef: browsePeel,
3728
+ onTyping: setTyping,
3678
3729
  mergedUrls,
3679
3730
  leavingUrls,
3680
3731
  transitSince,
@@ -3712,6 +3763,10 @@ var App = ({
3712
3763
  item: overlay.item,
3713
3764
  kind: overlay.kind,
3714
3765
  login: state.login,
3766
+ registerPeel: (peel) => {
3767
+ drillPeel.current = peel;
3768
+ },
3769
+ onTyping: setTyping,
3715
3770
  onBack: toBrowse,
3716
3771
  onRefresh: applyOrRefresh,
3717
3772
  onRemove: markLeaving,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.56.0",
3
+ "version": "0.57.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",
@@ -48,8 +48,8 @@
48
48
  "react": ">=19"
49
49
  },
50
50
  "dependencies": {
51
- "@kud/gh": "0.16.0",
52
- "@kud/gh-workflow": "0.12.0",
51
+ "@kud/gh": "0.17.0",
52
+ "@kud/gh-workflow": "0.12.1",
53
53
  "@kud/ink-ui": "0.29.0",
54
54
  "zx": "8.8.5"
55
55
  },