@kud/gh-ink 0.61.2 → 0.62.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
@@ -242,7 +242,54 @@ type PrRowProps = {
242
242
  motion?: RowMotion;
243
243
  /** In order, left to right. Charged against the width budget either way. */
244
244
  announcements?: readonly RowAnnouncement[];
245
+ /**
246
+ * The trailing columns' widths across the section — see `trailingColumnsOf`.
247
+ * Absent, the row measures itself, which aligns nothing but draws the same
248
+ * cells a host that never measured always got.
249
+ */
250
+ columns?: TrailingColumns;
251
+ /** Trailing columns the section has given up — see `sectionShedOf`. */
252
+ shed?: TrailingShed;
245
253
  };
254
+ /**
255
+ * The trailing block, in reading order: how contested, how big, how recent.
256
+ * Threads lead because they are the one cell that makes a claim on you; age
257
+ * ends the row so every row ends on the date.
258
+ */
259
+ declare const TRAILING: readonly ["threads", "size", "age"];
260
+ /** Width of each trailing column, in terminal cells. Zero draws no column. */
261
+ type TrailingColumns = Record<(typeof TRAILING)[number], number>;
262
+ /** Which trailing columns a whole section has given up. */
263
+ type TrailingShed = Partial<Record<(typeof TRAILING)[number], boolean>>;
264
+ /**
265
+ * The widest value each trailing column holds across these rows, measured once
266
+ * by the list — the same move as `uniformLabels` and `markerCols`. It is what
267
+ * makes the numbers line up: a column as wide as its widest value, right-
268
+ * aligned, so digits sit under digits and the eye can run straight down it.
269
+ * A column no row fills measures zero and is not drawn at all, which is what
270
+ * keeps this from becoming GitHub's grid of `—`.
271
+ *
272
+ * Code points rather than `.length`: every value here is ASCII or a single
273
+ * BMP glyph, so the two agree today, and a surrogate pair is the day they
274
+ * would not.
275
+ */
276
+ declare const trailingColumnsOf: (items: readonly GHItem[]) => TrailingColumns;
277
+ type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed">;
278
+ /**
279
+ * Which trailing columns the section has to give up, so that every row gives up
280
+ * the same ones. Each row is laid out alone and the drops are unioned: if any
281
+ * row needs its size cell gone to keep a readable title, the size column goes
282
+ * for all of them.
283
+ *
284
+ * Forcing a drop on a row that did not need it only ever hands that row more
285
+ * room, so it can never push the row further down the ladder than it went on
286
+ * its own — the union is stable in one pass.
287
+ *
288
+ * Announcements are left out on purpose. A MERGED pill lives for three seconds
289
+ * on a row already leaving, and letting it reshape the whole section for those
290
+ * three seconds would move every row under the one you just acted on.
291
+ */
292
+ declare const sectionShedOf: (rows: readonly Pick<LayoutInput, "item" | "prefix">[], context: Omit<LayoutInput, "item" | "prefix" | "announcements" | "shed">) => TrailingShed;
246
293
  /**
247
294
  * One pull request or issue, as a single line of a list.
248
295
  *
@@ -253,7 +300,7 @@ type PrRowProps = {
253
300
  * arrival, merge and departure belongs to whoever owns the list, and reaches
254
301
  * the row as `icon`, `motion` and `announcements` after the decisions are made.
255
302
  */
256
- declare const PrRow: ({ item, active, login, cols, icon: iconOverride, prefix, uniformLabels, motion, announcements, }: PrRowProps) => React.JSX.Element;
303
+ declare const PrRow: ({ item, active, login, cols, icon: iconOverride, prefix, uniformLabels, motion, announcements, columns, shed, }: PrRowProps) => React.JSX.Element;
257
304
 
258
305
  /**
259
306
  * One state per thing, from a closed set the strip knows how to draw. The host
@@ -713,4 +760,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
713
760
  */
714
761
  declare const parsePatterns: (value: string) => string[];
715
762
 
716
- export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
763
+ export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type LiveLabel, MIN_SIDEBAR_COLS, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
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 } from 'ink';
3
- import { colors, ScrollView, TextInput, Pill, useAppKeys, LoadingScreen, StatusMessage, FooterHints, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, Pill, useAppKeys, LoadingScreen, StatusMessage, FooterHints, useFilterMode, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } 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 { PIN_MARK, sizeOf, sizePartsOf, depthOf, impliedLabels, labelPriority, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin } from '@kud/gh-workflow';
@@ -475,9 +475,11 @@ var HealthPanel = ({
475
475
  const safeCursor = Math.min(cursor, Math.max(0, focusCount - 1));
476
476
  const selected = safeCursor < checks.length ? checks[safeCursor] : void 0;
477
477
  const selectedReviewer = safeCursor >= checks.length ? reviewers[safeCursor - checks.length] : void 0;
478
+ const noteTimer = useRef(null);
478
479
  const flash = (msg) => {
479
480
  setNote(msg);
480
- setTimeout(() => setNote(null), 2500);
481
+ if (noteTimer.current) clearTimeout(noteTimer.current);
482
+ noteTimer.current = setTimeout(() => setNote(null), 1500);
481
483
  };
482
484
  const act = async (msg, run, onSuccess) => {
483
485
  setBusy(true);
@@ -831,41 +833,56 @@ var Backdrop = ({
831
833
  children
832
834
  }
833
835
  ) : children });
834
- var PrRow = ({
836
+ var threadsLabelOf = (item) => item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
837
+ var agePairOf = (item) => item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
838
+ var ageLabelOf = (item) => {
839
+ const pair = agePairOf(item);
840
+ return pair ? `${pair.activity} (${pair.lifetime})` : item.age;
841
+ };
842
+ var TRAILING = ["threads", "size", "age"];
843
+ var cellsOf = (s) => [...s].length;
844
+ var trailingColumnsOf = (items) => items.reduce(
845
+ (w, item) => ({
846
+ threads: Math.max(w.threads, cellsOf(threadsLabelOf(item))),
847
+ size: Math.max(w.size, cellsOf(sizeOf(item) ?? "")),
848
+ age: Math.max(w.age, cellsOf(ageLabelOf(item) ?? ""))
849
+ }),
850
+ { threads: 0, size: 0, age: 0 }
851
+ );
852
+ var sectionShedOf = (rows, context) => rows.reduce((shed, row) => {
853
+ const { givingUp } = layoutOf({ ...context, ...row });
854
+ return {
855
+ threads: shed.threads || givingUp.threads,
856
+ size: shed.size || givingUp.size,
857
+ age: shed.age || givingUp.age
858
+ };
859
+ }, {});
860
+ var layoutOf = ({
835
861
  item,
836
- active,
837
862
  login,
838
863
  cols,
839
- icon: iconOverride,
840
864
  prefix = "",
841
865
  uniformLabels = [],
842
- motion,
843
- announcements = []
866
+ announcements = [],
867
+ columns,
868
+ shed = {}
844
869
  }) => {
845
- const backdropped = useBackdropped();
846
- const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
847
- const { glyph: icon, color } = iconOverride ?? {
848
- glyph: healthIcon,
849
- color: healthColor2
850
- };
851
- const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
852
- const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
853
870
  const numStr = `#${item.number}`.padEnd(7);
854
871
  const showAuthor = !!item.author && item.author !== login;
855
- const sizeLabel = sizeOf(item);
856
872
  const sizeParts = sizePartsOf(item);
857
- const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
858
- const agePair = item.activityAge && item.activityAge !== item.age ? { activity: item.activityAge, lifetime: item.age } : null;
859
- const ageLabel2 = agePair ? `${agePair.activity} (${agePair.lifetime})` : item.age;
873
+ const unresolvedLabel = threadsLabelOf(item);
874
+ const agePair = agePairOf(item);
875
+ const ageLabel2 = ageLabelOf(item);
876
+ const own = columns ?? trailingColumnsOf([item]);
860
877
  const pillCaps = announcements.length * 2;
861
878
  const repoLabel = depthOf(item) > 0 ? item.repo : "";
862
879
  const implied = [...impliedLabels(item.repo), ...uniformLabels];
863
880
  const labelNames = (item.labels ?? []).filter((l) => !implied.includes(l)).sort((a, b) => labelPriority(a) - labelPriority(b) || a.localeCompare(b)).slice(0, 2);
864
881
  const givingUp = {
865
882
  author: false,
866
- size: false,
867
- threads: false,
868
- age: false,
883
+ size: !!shed.size,
884
+ threads: !!shed.threads,
885
+ age: !!shed.age,
869
886
  repo: false,
870
887
  labels: 0
871
888
  };
@@ -887,16 +904,14 @@ var PrRow = ({
887
904
  return `\uF02B ${fitted.join(", ")}`;
888
905
  };
889
906
  const widthOf2 = () => {
890
- const suffix = [
891
- givingUp.age ? "" : ageLabel2 || "",
892
- // ASCII only, so no PUA double-width correction — see the label cell.
893
- givingUp.size ? "" : sizeLabel || "",
894
- givingUp.threads ? "" : unresolvedLabel,
895
- showAuthor && !givingUp.author ? `by ${item.author}` : "",
896
- ...announcements.map((a) => a.label)
897
- ].filter(Boolean).join(" ");
907
+ const block = TRAILING.reduce(
908
+ (w, k) => givingUp[k] || own[k] === 0 ? w : w + 2 + own[k],
909
+ 0
910
+ );
911
+ const author = showAuthor && !givingUp.author ? ` by ${item.author}`.length : 0;
912
+ const pills = announcements.reduce((w, a) => w + 2 + a.label.length, 0);
898
913
  const cell = labelCell();
899
- return 2 + prefix.length + 2 + 2 + 7 + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + suffix.length + pillCaps + 6;
914
+ return 2 + prefix.length + 2 + 2 + 7 + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + author + block + pills + pillCaps + 4;
900
915
  };
901
916
  const MIN_TITLE = 24;
902
917
  for (const give of [
@@ -913,7 +928,64 @@ var PrRow = ({
913
928
  }
914
929
  const labelLabel = labelCell();
915
930
  const titleMax = Math.max(1, cols - widthOf2());
916
- return /* @__PURE__ */ jsxs(Box, { children: [
931
+ return {
932
+ numStr,
933
+ showAuthor,
934
+ sizeParts,
935
+ unresolvedLabel,
936
+ agePair,
937
+ ageLabel: ageLabel2,
938
+ repoLabel,
939
+ givingUp,
940
+ labelLabel,
941
+ titleMax,
942
+ columns: own
943
+ };
944
+ };
945
+ var PrRow = ({
946
+ item,
947
+ active,
948
+ login,
949
+ cols,
950
+ icon: iconOverride,
951
+ prefix = "",
952
+ uniformLabels = [],
953
+ motion,
954
+ announcements = [],
955
+ columns,
956
+ shed
957
+ }) => {
958
+ const backdropped = useBackdropped();
959
+ const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
960
+ const { glyph: icon, color } = iconOverride ?? {
961
+ glyph: healthIcon,
962
+ color: healthColor2
963
+ };
964
+ const spokeLast = !!login && !!item.lastActor && item.lastActor === login;
965
+ const [turnIcon, turnColor] = item.pinned ? [PIN_MARK, colors.accent] : !login || !item.lastActor || spokeLast ? [" ", colors.muted] : ["\u2190", colors.accent];
966
+ const {
967
+ numStr,
968
+ showAuthor,
969
+ sizeParts,
970
+ unresolvedLabel,
971
+ agePair,
972
+ ageLabel: ageLabel2,
973
+ repoLabel,
974
+ givingUp,
975
+ labelLabel,
976
+ titleMax,
977
+ columns: block
978
+ } = layoutOf({
979
+ item,
980
+ login,
981
+ cols,
982
+ prefix,
983
+ uniformLabels,
984
+ announcements,
985
+ columns,
986
+ shed
987
+ });
988
+ return /* @__PURE__ */ jsxs(Box, { width: cols, children: [
917
989
  /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
918
990
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
919
991
  /* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
@@ -930,25 +1002,43 @@ var PrRow = ({
930
1002
  ),
931
1003
  labelLabel ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: labelLabel + " " }) : null,
932
1004
  repoLabel && !givingUp.repo ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: repoLabel }) : null,
933
- sizeParts && !givingUp.size ? /* @__PURE__ */ jsxs(Text3, { children: [
934
- " ",
935
- /* @__PURE__ */ jsx(Text3, { color: colors.success, children: sizeParts.added }),
936
- " ",
937
- /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
938
- ] }) : null,
939
- unresolvedLabel && !givingUp.threads ? /* @__PURE__ */ jsx(
940
- Text3,
1005
+ showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1006
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
1007
+ block.threads > 0 && !givingUp.threads ? /* @__PURE__ */ jsx(
1008
+ Box,
941
1009
  {
942
- bold: !spokeLast,
943
- color: spokeLast ? colors.secondary : colors.accent,
944
- children: " " + unresolvedLabel
1010
+ marginLeft: 2,
1011
+ width: block.threads,
1012
+ justifyContent: "flex-end",
1013
+ flexShrink: 0,
1014
+ children: unresolvedLabel ? /* @__PURE__ */ jsx(
1015
+ Text3,
1016
+ {
1017
+ bold: !spokeLast,
1018
+ color: spokeLast ? colors.secondary : colors.accent,
1019
+ children: unresolvedLabel
1020
+ }
1021
+ ) : null
945
1022
  }
946
1023
  ) : null,
947
- showAuthor && !givingUp.author ? /* @__PURE__ */ jsx(Text3, { dimColor: true, italic: true, children: " by " + item.author }) : null,
948
- ageLabel2 && !givingUp.age ? agePair ? /* @__PURE__ */ jsxs(Fragment, { children: [
949
- /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + agePair.activity }),
1024
+ block.size > 0 && !givingUp.size ? /* @__PURE__ */ jsx(
1025
+ Box,
1026
+ {
1027
+ marginLeft: 2,
1028
+ width: block.size,
1029
+ justifyContent: "flex-end",
1030
+ flexShrink: 0,
1031
+ children: sizeParts ? /* @__PURE__ */ jsxs(Text3, { children: [
1032
+ /* @__PURE__ */ jsx(Text3, { color: colors.success, children: sizeParts.added }),
1033
+ " ",
1034
+ /* @__PURE__ */ jsx(Text3, { color: colors.error, children: sizeParts.removed })
1035
+ ] }) : null
1036
+ }
1037
+ ) : null,
1038
+ block.age > 0 && !givingUp.age ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, width: block.age, flexShrink: 0, children: agePair ? /* @__PURE__ */ jsxs(Text3, { children: [
1039
+ /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: agePair.activity }),
950
1040
  /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` (${agePair.lifetime})` })
951
- ] }) : /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: " " + ageLabel2 }) : null,
1041
+ ] }) : ageLabel2 ? /* @__PURE__ */ jsx(Text3, { color: colors.secondary, children: ageLabel2 }) : null }) : null,
952
1042
  backdropped ? null : announcements.map((a) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
953
1043
  /* @__PURE__ */ jsx(Text3, { children: " " }),
954
1044
  /* @__PURE__ */ jsx(Pill, { color: a.color, children: a.label })
@@ -2189,7 +2279,7 @@ var RepoHeaderRow = ({
2189
2279
  /* @__PURE__ */ jsx(Text3, { dimColor: !active, children: " " + rule })
2190
2280
  ] });
2191
2281
  };
2192
- var MERGED_HOLD_MS = 3e3;
2282
+ var MERGED_HOLD_MS = 2400;
2193
2283
  var MERGED_FRAME_MS = 150;
2194
2284
  var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
2195
2285
  var MERGED_COLOUR = "#A371F7";
@@ -2223,7 +2313,7 @@ var TRANSIT_LABEL = {
2223
2313
  out: "gone"};
2224
2314
  var isDeparture = (t) => t === "out" || t === "moved-out";
2225
2315
  var isArrival = (t) => t === "in" || t === "moved-in";
2226
- var LEAVING_HOLD_MS = 2500;
2316
+ var LEAVING_HOLD_MS = 1500;
2227
2317
  var tabMarker = (marked, id, frame = 0) => marked.has(id) ? `${TAB_PULSE[frame % TAB_PULSE.length]} ` : " ";
2228
2318
  var TAB_PULSE = ["\xB7", "\u25CB", "\u25CE", "\u25C9", "\u25CE", "\u25CB"];
2229
2319
  var ItemRow = ({
@@ -2240,7 +2330,9 @@ var ItemRow = ({
2240
2330
  sparkFrame = 0,
2241
2331
  cols = COLS,
2242
2332
  markerCols = 0,
2243
- uniformLabels = []
2333
+ uniformLabels = [],
2334
+ columns,
2335
+ shed
2244
2336
  }) => {
2245
2337
  const backdropped = useBackdropped();
2246
2338
  const transient = leaving ? "out" : refreshMark;
@@ -2331,6 +2423,8 @@ var ItemRow = ({
2331
2423
  cols,
2332
2424
  prefix,
2333
2425
  uniformLabels,
2426
+ columns,
2427
+ shed,
2334
2428
  icon,
2335
2429
  motion,
2336
2430
  announcements
@@ -2703,8 +2797,6 @@ var BrowseScreen = ({
2703
2797
  }, [activeId]);
2704
2798
  const [flash, setFlash] = useState(null);
2705
2799
  const menu = useActionMenu();
2706
- const [search, setSearch] = useState(null);
2707
- const [searchInput, setSearchInput] = useState(false);
2708
2800
  const [repoFilter, setRepoFilter] = useState(/* @__PURE__ */ new Set());
2709
2801
  const [repoPicker, setRepoPicker] = useState(false);
2710
2802
  const [help, setHelp] = useState(false);
@@ -2712,6 +2804,11 @@ var BrowseScreen = ({
2712
2804
  const [helpRange, setHelpRange] = useState({ max: 0, page: 1 });
2713
2805
  const [explain, setExplain] = useState(false);
2714
2806
  const [palette, setPalette] = useState(null);
2807
+ const filter = useFilterMode({
2808
+ isActive: !hidden && palette === null && !help && !explain && !repoPicker && menu.actions === null
2809
+ });
2810
+ const search = filter.term;
2811
+ const searchInput = filter.typing;
2715
2812
  const filterActive = search != null || repoFilter.size > 0;
2716
2813
  const reserveCiRow = ciStatusState != null;
2717
2814
  const reserveStripRow = stripState != null;
@@ -2801,9 +2898,11 @@ var BrowseScreen = ({
2801
2898
  ) : [];
2802
2899
  const hasMore = viewStart + visibleCount < section.items.length;
2803
2900
  const activeItem = section.items[cursor];
2901
+ const flashTimer = useRef(null);
2804
2902
  const showFlash = (msg) => {
2805
2903
  setFlash(msg);
2806
- setTimeout(() => setFlash(null), 2e3);
2904
+ if (flashTimer.current) clearTimeout(flashTimer.current);
2905
+ flashTimer.current = setTimeout(() => setFlash(null), 1500);
2807
2906
  };
2808
2907
  const markedTabs = useMemo(() => {
2809
2908
  const out = /* @__PURE__ */ new Set();
@@ -2830,7 +2929,7 @@ var BrowseScreen = ({
2830
2929
  if (repoPicker) return setRepoPicker(false), true;
2831
2930
  if (railActive) return setRailFocus(false), true;
2832
2931
  if (menu.actions !== null) return menu.close(), true;
2833
- if (search != null) return setSearch(null), true;
2932
+ if (search != null) return filter.clear(), true;
2834
2933
  if (repoFilter.size > 0) return setRepoFilter(/* @__PURE__ */ new Set()), true;
2835
2934
  return false;
2836
2935
  };
@@ -2846,6 +2945,13 @@ var BrowseScreen = ({
2846
2945
  }, [searchInput, palette, onTyping]);
2847
2946
  const railCols = railWidth(COLS);
2848
2947
  const listCols = showRail ? COLS - railCols : COLS;
2948
+ const trailingColumns = trailingColumnsOf(labelBearing);
2949
+ const trailingShed = sectionShedOf(
2950
+ section.items.flatMap(
2951
+ (item, i) => item.kind === "pr" || item.kind === "issue" ? [{ item, prefix: treePrefix(section.items, i) }] : []
2952
+ ),
2953
+ { login, cols: listCols, uniformLabels, columns: trailingColumns }
2954
+ );
2849
2955
  const [sparkFrame, setSparkFrame] = useState(0);
2850
2956
  const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || // Any MARKED TAB, not just rows on screen. The scope used to be this tab's
2851
2957
  // own items, which was right while the animation lived on the rows and is
@@ -2972,23 +3078,7 @@ var BrowseScreen = ({
2972
3078
  }
2973
3079
  return;
2974
3080
  }
2975
- if (searchInput && !key.upArrow && !key.downArrow) {
2976
- if (key.return) return setSearchInput(false);
2977
- if (key.escape) {
2978
- setSearch(null);
2979
- return setSearchInput(false);
2980
- }
2981
- if (key.backspace || key.delete)
2982
- return setSearch((s) => (s ?? "").slice(0, -1));
2983
- if (input && !key.ctrl && !key.meta && !key.tab)
2984
- return setSearch((s) => (s ?? "") + input);
2985
- return;
2986
- }
2987
- if (input === "/") {
2988
- setSearch("");
2989
- setSearchInput(true);
2990
- return;
2991
- }
3081
+ if (searchInput && !key.upArrow && !key.downArrow) return;
2992
3082
  if (input === "f" && allRepos.length > 0) {
2993
3083
  setRepoCursor(0);
2994
3084
  setRepoPicker(true);
@@ -3516,7 +3606,7 @@ var BrowseScreen = ({
3516
3606
  /* @__PURE__ */ jsx(Text3, { color: colors.info, children: " / " }),
3517
3607
  /* @__PURE__ */ jsx(Text3, { children: search }),
3518
3608
  searchInput ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: "\u258F" }) : null,
3519
- /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u2191\u2193 move \xB7 \u21B5 accept \xB7 esc clear" : " esc clear"}` })
3609
+ /* @__PURE__ */ jsx(Text3, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""} ${filter.hints.map(([k, label]) => `${k} ${label}`).join(" \xB7 ")}` })
3520
3610
  ] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
3521
3611
  /* @__PURE__ */ jsx(Text3, { color: colors.accent, children: " \u25C9 " }),
3522
3612
  /* @__PURE__ */ jsx(Text3, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
@@ -3554,6 +3644,8 @@ var BrowseScreen = ({
3554
3644
  cols: listCols,
3555
3645
  markerCols,
3556
3646
  uniformLabels,
3647
+ columns: trailingColumns,
3648
+ shed: trailingShed,
3557
3649
  gap: i > 0 && gapsAbove(section.items, viewStart + i)
3558
3650
  },
3559
3651
  `${viewStart + i}:${rowKey(item)}`
@@ -4140,4 +4232,4 @@ var matchesFilter = (repo, filter) => {
4140
4232
  };
4141
4233
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
4142
4234
 
4143
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, truncate, useActionMenu, windowCount, withHeaders, withoutItem, workCount, writeCache };
4235
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, 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.61.2",
3
+ "version": "0.62.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.18.0",
52
52
  "@kud/gh-workflow": "0.13.1",
53
- "@kud/ink-ui": "0.30.0",
53
+ "@kud/ink-ui": "0.31.0",
54
54
  "zx": "8.8.5"
55
55
  },
56
56
  "devDependencies": {