@kud/gh-ink 0.75.0 → 0.75.2

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
@@ -196,7 +196,11 @@ declare const SidePanel: ({ sidebar, liveLabel, width, height, focused, cursor,
196
196
  type FocusSlot = {
197
197
  /** The mark in front of the row, accent by default. */
198
198
  mark?: string;
199
- /** The mark's colour. Defaults to the accent. */
199
+ /**
200
+ * The mark's colour in the list gutter (`focusCellFor`). Defaults to the
201
+ * accent. The ready row's pill takes its accent from its own
202
+ * `variant="accent"`, so this never reaches the pill.
203
+ */
200
204
  markColor?: string;
201
205
  /** What the row is a slot OF, in the host's words: `focus`, say. */
202
206
  label: string;
@@ -217,6 +221,7 @@ type FocusSlot = {
217
221
  };
218
222
  };
219
223
  declare const FOCUS_MARK = "\uDB81\uDCFE";
224
+ declare const FOCUS_JUMP_KEY = "g";
220
225
  declare const DEFAULT_FOCUS_LABEL = "focus";
221
226
  declare const DEFAULT_FOCUS_EMPTY = "nothing to focus on";
222
227
  /**
@@ -256,7 +261,7 @@ declare const focusRefColor: (focus: Pick<FocusSlot, "ref" | "refColor">) => str
256
261
  * with nothing to point at. Either prop (`label`/`empty`) reserves the row;
257
262
  * with neither and no focus value the caller draws nothing at all.
258
263
  */
259
- declare const FocusSlotLine: ({ focus, label, empty, width, }: {
264
+ declare const FocusSlotLine: ({ focus, label, empty, width, jumpKey, }: {
260
265
  /** `undefined` while the first answer is out; `null` when it came back empty. */
261
266
  focus: FocusSlot | null | undefined;
262
267
  /** What to call the row. The host's word; defaults to `focus`. */
@@ -265,6 +270,12 @@ declare const FocusSlotLine: ({ focus, label, empty, width, }: {
265
270
  empty?: string;
266
271
  /** Columns the row may use. */
267
272
  width: number;
273
+ /**
274
+ * The key that jumps to the focus row, drawn as a quiet hint after the
275
+ * reason. Ready rows only: loading and empty have no row to jump to, so
276
+ * they never draw it even when it is set.
277
+ */
278
+ jumpKey?: string;
268
279
  }) => React.JSX.Element;
269
280
 
270
281
  /**
@@ -1124,4 +1135,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
1124
1135
  */
1125
1136
  declare const parsePatterns: (value: string) => string[];
1126
1137
 
1127
- export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, type DetailContext, type ExplainSection, type ExtensionTarget, FOCUS_MARK, type FocusGutter, type FocusSlot, FocusSlotLine, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraAvailableTransition, type JiraTransition, type JiraTransitionsFor, type LiveLabel, LookupMiss, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TabForStatus, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
1138
+ export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, type DetailContext, type ExplainSection, type ExtensionTarget, FOCUS_JUMP_KEY, FOCUS_MARK, type FocusGutter, type FocusSlot, FocusSlotLine, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraAvailableTransition, type JiraTransition, type JiraTransitionsFor, type LiveLabel, LookupMiss, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TabForStatus, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React2, { createContext, useState, useRef, useContext, useEffect, useCallback, useMemo } from 'react';
2
2
  import { useWindowSize, useInput, Text, Box } from 'ink';
3
- import { colors, ScrollView, TextInput, Pill, getIconMode, FooterHints, useAppKeys, LoadingScreen, StatusMessage, useFilterMode, useListCursor, CommandPalette, Tabs, ProgressBar, pillWidth } from '@kud/ink-ui';
3
+ import { colors, ScrollView, TextInput, Pill, getIconMode, FooterHints, useAppKeys, LoadingScreen, pillWidth, StatusMessage, useFilterMode, useListCursor, CommandPalette, Tabs, ProgressBar } from '@kud/ink-ui';
4
4
  import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer, parseRef, formatRef, fetchItemNode } 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, refCandidates, resolveRef } from '@kud/gh-workflow';
@@ -1694,6 +1694,7 @@ var failureNotice = (failed) => {
1694
1694
  return `\u2717 Couldn't ${newest.action} ${newest.label}: ${newest.failure?.reason ?? "no reason reported"}${more} w restore`;
1695
1695
  };
1696
1696
  var FOCUS_MARK = "\u{F04FE}";
1697
+ var FOCUS_JUMP_KEY = "g";
1697
1698
  var DEFAULT_FOCUS_LABEL = "focus";
1698
1699
  var DEFAULT_FOCUS_EMPTY = "nothing to focus on";
1699
1700
  var focusCellFor = (focus, url) => {
@@ -1703,23 +1704,34 @@ var focusCellFor = (focus, url) => {
1703
1704
  };
1704
1705
  var focusRefColor = (focus) => focus.refColor ?? (focus.ref.startsWith("#") ? colors.accent : colors.ticket);
1705
1706
  var cells = (s) => [...s].length;
1706
- var titleBudgetOf = (focus, label, width) => {
1707
+ var titleBudgetOf = (focus, label, width, jumpKey) => {
1708
+ const hint = jumpKey ? jumpKey : void 0;
1707
1709
  const mark = focus.mark ?? FOCUS_MARK;
1708
- const head = 2 + cells(mark) + 1 + cells(label) + 2 + (focus.marker ? cells(focus.marker) + 1 : 0) + cells(focus.ref) + 2;
1710
+ const head = 2 + pillWidth(`${mark} ${label}`) + 2 + (focus.marker ? cells(focus.marker) + 1 : 0) + cells(focus.ref) + 2;
1711
+ const hintCells = hint ? 3 + cells(hint) + 5 : 0;
1709
1712
  const tail = 3 + cells(focus.reason);
1710
- const titleMax = width - head - tail;
1713
+ const titleMax = width - head - tail - hintCells;
1711
1714
  if (titleMax >= 1)
1712
- return { title: truncate(focus.title, titleMax), reason: focus.reason };
1715
+ return { title: truncate(focus.title, titleMax), reason: focus.reason, hint };
1716
+ const reasonMax = width - head - 3 - 1 - hintCells;
1717
+ if (hint === void 0 || reasonMax >= 1)
1718
+ return {
1719
+ title: "",
1720
+ reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1 - hintCells)),
1721
+ hint
1722
+ };
1713
1723
  return {
1714
1724
  title: "",
1715
- reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1))
1725
+ reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1)),
1726
+ hint: void 0
1716
1727
  };
1717
1728
  };
1718
1729
  var FocusSlotLine = ({
1719
1730
  focus,
1720
1731
  label = DEFAULT_FOCUS_LABEL,
1721
1732
  empty = DEFAULT_FOCUS_EMPTY,
1722
- width
1733
+ width,
1734
+ jumpKey
1723
1735
  }) => {
1724
1736
  if (focus === void 0)
1725
1737
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
@@ -1732,15 +1744,20 @@ var FocusSlotLine = ({
1732
1744
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ${empty}` })
1733
1745
  ] });
1734
1746
  const mark = focus.mark ?? FOCUS_MARK;
1735
- const { title, reason } = titleBudgetOf(focus, label, width);
1747
+ const { title, reason, hint } = titleBudgetOf(focus, label, width, jumpKey);
1736
1748
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1737
1749
  /* @__PURE__ */ jsx(Text, { children: " " }),
1738
- /* @__PURE__ */ jsx(Text, { color: focus.markColor ?? colors.accent, bold: true, children: `${mark} ` }),
1739
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ` }),
1750
+ /* @__PURE__ */ jsx(Pill, { variant: "accent", tone: "tonal", children: `${mark} ${label}` }),
1751
+ /* @__PURE__ */ jsx(Text, { children: " " }),
1740
1752
  focus.marker ? /* @__PURE__ */ jsx(Text, { children: `${focus.marker} ` }) : null,
1741
1753
  /* @__PURE__ */ jsx(Text, { color: focusRefColor(focus), bold: true, children: `${focus.ref} ` }),
1742
1754
  /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
1743
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${reason}` })
1755
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${reason}` }),
1756
+ hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
1757
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
1758
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: hint }),
1759
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " jump" })
1760
+ ] }) : null
1744
1761
  ] });
1745
1762
  };
1746
1763
  var quietly = $({ quiet: true });
@@ -3324,7 +3341,7 @@ var HelpModal = ({
3324
3341
  ["f", "filter by repo"],
3325
3342
  ["r", "refresh"],
3326
3343
  ["w", "restore a change that failed"],
3327
- ...hasFocus ? [["g", "jump to focus"]] : [],
3344
+ ...hasFocus ? [[FOCUS_JUMP_KEY, "jump to focus"]] : [],
3328
3345
  ...extensionLegend(extensions),
3329
3346
  ["?", "this help"],
3330
3347
  ["q", "quit"]
@@ -3969,7 +3986,7 @@ var BrowseScreen = ({
3969
3986
  onRefresh?.();
3970
3987
  return;
3971
3988
  }
3972
- if (input === "g" && focus) {
3989
+ if (input === FOCUS_JUMP_KEY && focus) {
3973
3990
  const tabId = focus.target.tab;
3974
3991
  const at = localSections.findIndex((s) => s.id === tabId);
3975
3992
  if (at === -1) return;
@@ -4497,7 +4514,8 @@ var BrowseScreen = ({
4497
4514
  focus,
4498
4515
  label: focusLabel,
4499
4516
  empty: focusEmpty,
4500
- width: COLS
4517
+ width: COLS,
4518
+ jumpKey: FOCUS_JUMP_KEY
4501
4519
  }
4502
4520
  ) : null,
4503
4521
  skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
@@ -4710,7 +4728,15 @@ var App = ({
4710
4728
  onSettled
4711
4729
  }) => {
4712
4730
  const { rows } = useWindowSize();
4713
- const [state, setState] = useState({ phase: "loading" });
4731
+ const [launchCache] = useState(() => cacheKey ? readCache(cacheKey) : null);
4732
+ const launchPainted = !!launchCache && launchCache.sections.length > 0;
4733
+ const [state, setState] = useState(
4734
+ () => launchPainted && launchCache ? {
4735
+ phase: "browse",
4736
+ sections: launchCache.sections,
4737
+ login: launchCache.login
4738
+ } : { phase: "loading" }
4739
+ );
4714
4740
  const browsePeel = useRef(null);
4715
4741
  const drillPeel = useRef(null);
4716
4742
  const [typing, setTyping] = useState(false);
@@ -4740,7 +4766,9 @@ var App = ({
4740
4766
  });
4741
4767
  const [pending, setPending] = useState(null);
4742
4768
  const [refreshing, setRefreshing] = useState(false);
4743
- const [fetchedAt, setFetchedAt] = useState(null);
4769
+ const [fetchedAt, setFetchedAt] = useState(
4770
+ launchPainted && launchCache ? launchCache.at : null
4771
+ );
4744
4772
  const [refreshError, setRefreshError] = useState(null);
4745
4773
  const [ciStatusState, setCiStatusState] = useState({
4746
4774
  kind: "loading"
@@ -4750,6 +4778,7 @@ var App = ({
4750
4778
  return sameCiStatusState(prev, next) ? prev : next;
4751
4779
  });
4752
4780
  const hasStrip = !!stripFetcher;
4781
+ const hasFocusSlot = focusLabel !== void 0 || focusEmpty !== void 0;
4753
4782
  const [stripState, setStripState] = useState({
4754
4783
  kind: "loading"
4755
4784
  });
@@ -4758,8 +4787,12 @@ var App = ({
4758
4787
  if (prev.kind !== "ready") return next;
4759
4788
  return stripSignature(prev.strip) === stripSignature(strip) ? prev : next;
4760
4789
  });
4761
- const displayedKey = useRef("");
4762
- const displayedSections = useRef([]);
4790
+ const displayedKey = useRef(
4791
+ launchPainted && launchCache ? signatureOf(launchCache.sections) : ""
4792
+ );
4793
+ const displayedSections = useRef(
4794
+ launchPainted && launchCache ? launchCache.sections : []
4795
+ );
4763
4796
  const [transients, setTransients] = useState(NO_TRANSIENTS);
4764
4797
  const transitTabs = useRef(/* @__PURE__ */ new Map());
4765
4798
  const [heldSince, setHeldSince] = useState(NO_HOLDS);
@@ -5024,21 +5057,7 @@ var App = ({
5024
5057
  setTimeout(() => applyOrRefresh(), 1500);
5025
5058
  };
5026
5059
  useEffect(() => {
5027
- const cached = cacheKey ? readCache(cacheKey) : null;
5028
- const painted = !!cached && cached.sections.length > 0;
5029
- if (painted && cached) {
5030
- displayedKey.current = signatureOf(cached.sections);
5031
- displayedSections.current = cached.sections;
5032
- setFetchedAt(cached.at);
5033
- if (cached.sidebar) setSidebar(cached.sidebar);
5034
- if (cached.focus !== void 0) setFocus(cached.focus);
5035
- setState({
5036
- phase: "browse",
5037
- sections: cached.sections,
5038
- login: cached.login
5039
- });
5040
- }
5041
- if (!painted || !isFresh(cached)) revalidate();
5060
+ if (!launchPainted || !isFresh(launchCache)) revalidate();
5042
5061
  }, []);
5043
5062
  useEffect(() => {
5044
5063
  if (!hasCiStatus || !ciFetcher) return;
@@ -5114,8 +5133,12 @@ var App = ({
5114
5133
  () => cacheKey ? readCache(cacheKey)?.budget ?? null : null
5115
5134
  );
5116
5135
  const [appliedSummary, setAppliedSummary] = useState("");
5117
- const [sidebar, setSidebar] = useState(void 0);
5118
- const [focus, setFocus] = useState(void 0);
5136
+ const [sidebar, setSidebar] = useState(
5137
+ launchPainted ? launchCache?.sidebar : void 0
5138
+ );
5139
+ const [focus, setFocus] = useState(
5140
+ launchPainted ? launchCache?.focus : void 0
5141
+ );
5119
5142
  const [skippedForBudget, setSkippedForBudget] = useState(false);
5120
5143
  const [mergedUrls, setMergedUrls] = useState([]);
5121
5144
  const [leavingUrls, setLeavingUrls] = useState([]);
@@ -5168,7 +5191,7 @@ var App = ({
5168
5191
  width: COLS
5169
5192
  }
5170
5193
  ) : null,
5171
- focusLabel !== void 0 || focusEmpty !== void 0 ? /* @__PURE__ */ jsx(
5194
+ hasFocusSlot ? /* @__PURE__ */ jsx(
5172
5195
  FocusSlotLine,
5173
5196
  {
5174
5197
  focus: void 0,
@@ -5181,7 +5204,10 @@ var App = ({
5181
5204
  Box,
5182
5205
  {
5183
5206
  flexDirection: "column",
5184
- minHeight: Math.max(5, rows - 6 - (hasCiStatus ? 2 : 0)),
5207
+ minHeight: Math.max(
5208
+ 5,
5209
+ rows - 5 - (hasCiStatus ? 2 : 0) - (hasStrip ? 2 : 0) - (hasFocusSlot ? 2 : 0)
5210
+ ),
5185
5211
  children: state.phase === "loading" ? /* @__PURE__ */ jsx(LoadingScreen, { label: `Fetching ${title}\u2026` }) : /* @__PURE__ */ jsx(
5186
5212
  NoRowsScreen,
5187
5213
  {
@@ -5340,4 +5366,4 @@ var matchesFilter = (repo, filter) => {
5340
5366
  };
5341
5367
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
5342
5368
 
5343
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, FOCUS_MARK, FocusSlotLine, HealthPanel, LookupMiss, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
5369
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, FOCUS_JUMP_KEY, FOCUS_MARK, FocusSlotLine, HealthPanel, LookupMiss, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.75.0",
3
+ "version": "0.75.2",
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",
@@ -44,7 +44,7 @@
44
44
  "access": "public"
45
45
  },
46
46
  "peerDependencies": {
47
- "@kud/ink-ui": ">=0.39.0 <1",
47
+ "@kud/ink-ui": ">=0.41.0 <1",
48
48
  "ink": ">=7",
49
49
  "react": ">=19"
50
50
  },
@@ -54,7 +54,7 @@
54
54
  "zx": "8.8.5"
55
55
  },
56
56
  "devDependencies": {
57
- "@kud/ink-ui": "0.39.0",
57
+ "@kud/ink-ui": "0.41.0",
58
58
  "@types/node": "22.20.1",
59
59
  "@types/react": "19.2.17",
60
60
  "ink": "7.1.0",