@kud/gh-ink 0.22.1 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -201,7 +201,38 @@ declare const insertRepoHeaders: (items: GHItem[]) => AnyItem[];
201
201
  type Standing = "authored" | "queued" | "spoken";
202
202
  declare const whoseMove: (health: Health, sectionId: string, standing?: Standing) => "you" | "them";
203
203
  declare const layoutGHItems: (items: GHItem[], sectionId: string) => AnyItem[];
204
- declare const filterByOrigin: (sections: Section[], keep: "work" | "home", isWorkRepo: (repo: string) => boolean) => Section[];
204
+ /**
205
+ * A two-way repo split, when a host has one. `undefined` — the ordinary case —
206
+ * means no split at all: every row stands.
207
+ *
208
+ * The package deliberately does not NAME the sides. It hardcoded "work" and
209
+ * "home" until 2026-08-27: one reader's two lives compiled into a library
210
+ * anyone can install, printed in a header where no other host would recognise
211
+ * the word. The predicate, the side, and the word are all yours.
212
+ *
213
+ * Chosen once, by the host, before the first paint. There is no in-app toggle,
214
+ * because a key that flipped it could only ever put the inbox out of step with
215
+ * the scope the command was started in, with nothing on screen to explain the
216
+ * disagreement.
217
+ */
218
+ type OriginSplit = {
219
+ /** Rows whose repo this matches are one side; everything else is the other. */
220
+ match: (repo: string) => boolean;
221
+ /** Which side to show. */
222
+ show: "matched" | "rest";
223
+ /** What to call the visible side in the header. Omitted, nothing is shown. */
224
+ label?: string;
225
+ };
226
+ /**
227
+ * Keep only one side of a two-way repo split, leaving non-GH rows (task)
228
+ * untouched. Mirrors filterByRepos' gh/other split.
229
+ *
230
+ * `keep` is positional — "the ones that matched" or "the rest" — and never a
231
+ * name. This took the literal strings "work" and "home" until 2026-08-27, which
232
+ * is one reader's life compiled into a published library: no other host has
233
+ * those two categories, and several have none at all.
234
+ */
235
+ declare const filterByOrigin: (sections: Section[], keep: "matched" | "rest", match: (repo: string) => boolean) => Section[];
205
236
  declare const filterBySearch: (sections: Section[], query: string) => Section[];
206
237
  declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
207
238
  declare const withoutItem: (sections: Section[], target: GHItem) => Section[];
@@ -251,6 +282,21 @@ declare const CiStatusLine: ({ state, job, }: {
251
282
  state: CiStatusState;
252
283
  job?: string;
253
284
  }) => React.JSX.Element;
285
+ /**
286
+ * Indexes of task rows whose row has already appeared higher up the same list.
287
+ *
288
+ * A ticket with one PR needing you and another in review lands in two bands,
289
+ * which is right — the band reads each PR, not the ticket. What was wrong was
290
+ * drawing the second occurrence identically to the first, so one ticket read as
291
+ * two. The repeat keeps its key and drops its summary.
292
+ *
293
+ * Identity is the URL, never `key`. `key` is a LABEL, and on a task surface that
294
+ * is not a tracker (`life`, whose keys are categories like "Flat") one label
295
+ * legitimately heads several unrelated rows — keying on it hid the summary of
296
+ * every row but the first. A row with no url is never a repeat, because nothing
297
+ * identifies it.
298
+ */
299
+ declare const repeatedTaskRows: (items: readonly AnyItem[]) => Set<number>;
254
300
  declare const useActionMenu: () => {
255
301
  actions: Action[] | null;
256
302
  cursor: number;
@@ -268,7 +314,8 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
268
314
  actions: Action[];
269
315
  cursor: number;
270
316
  }) => React.JSX.Element;
271
- declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, includeWork, jiraBase, jiraKeyRe, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, extensions, tabHelp, emptyHint, }: {
317
+ declare const signatureOf: (sections: Section[]) => string;
318
+ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraTransitions, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, extensions, tabHelp, emptyHint, }: {
272
319
  fetcher: () => Promise<{
273
320
  sections: Section[];
274
321
  login: string;
@@ -277,17 +324,8 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, includeWo
277
324
  cacheKey?: string;
278
325
  title?: string;
279
326
  detailFor?: (ctx: DetailContext) => React.ReactNode;
280
- isWorkRepo?: (repo: string) => boolean;
281
- /**
282
- * Which side of the split to show, chosen ONCE by the host at launch — there
283
- * is no in-app toggle. `undefined` means no split: every row stands.
284
- *
285
- * The host decides from where the command was run, so the answer is settled
286
- * before the first paint and cannot change under the reader. A `w` key used to
287
- * flip it mid-session, which meant the inbox could disagree with the scope the
288
- * command had been started in, with nothing on screen explaining why.
289
- */
290
- includeWork?: boolean;
327
+ /** A two-way repo split, and what to call the side on screen. See OriginSplit. */
328
+ origin?: OriginSplit;
291
329
  tabHelp?: [string, string][];
292
330
  emptyHint?: string;
293
331
  jiraBase?: string;
@@ -406,4 +444,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
406
444
  */
407
445
  declare const parsePatterns: (value: string) => string[];
408
446
 
409
- export { type Action, ActionMenu, type AnyItem, App, COLS, type CiStatus, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, type GHDetail, type GHItem, HealthPanel, type HealthPanelProps, type InboxConfig, type InboxExtension, type JiraTransition, type RepoFilter, type RepoHeader, type RepoProfile, type Section, type ShowLess, type ShowMore, type Standing, type SubgroupHeader, type TaskRow, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
447
+ export { type Action, ActionMenu, type AnyItem, App, COLS, type CiStatus, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, type GHDetail, type GHItem, HealthPanel, type HealthPanelProps, type InboxConfig, type InboxExtension, type JiraTransition, type OriginSplit, type RepoFilter, type RepoHeader, type RepoProfile, type Section, type ShowLess, type ShowMore, type Standing, type SubgroupHeader, type TaskRow, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repeatedTaskRows, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
package/dist/index.js CHANGED
@@ -945,9 +945,9 @@ var layoutGHItems = (items, sectionId) => {
945
945
  ...insertRepoHeaders(rows)
946
946
  ]);
947
947
  };
948
- var filterByOrigin = (sections, keep, isWorkRepo) => sections.map((s) => {
948
+ var filterByOrigin = (sections, keep, match) => sections.map((s) => {
949
949
  const kept = s.items.filter(
950
- (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && (i.kind === "pr" || i.kind === "issue" ? keep === "work" ? isWorkRepo(i.repo) : !isWorkRepo(i.repo) : true)
950
+ (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && (i.kind === "pr" || i.kind === "issue" ? keep === "matched" ? match(i.repo) : !match(i.repo) : true)
951
951
  );
952
952
  const gh = kept.filter(
953
953
  (i) => i.kind === "pr" || i.kind === "issue"
@@ -1384,6 +1384,23 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1384
1384
  showFlash(`\u2713 Copied ${item.repo}`);
1385
1385
  }
1386
1386
  });
1387
+ if (item.kind === "pr" && item.standing === "queued" && login) {
1388
+ actions.push({
1389
+ label: "Remove me as reviewer",
1390
+ hint: "x",
1391
+ run: () => {
1392
+ onRemove?.(item);
1393
+ showFlash(`\u22EF Removing you from #${item.number}\u2026`);
1394
+ void quietly`gh pr edit ${item.number} --repo ${item.repo} --remove-reviewer ${login}`.then(() => {
1395
+ showFlash(`\u2713 Removed you as reviewer on #${item.number}`);
1396
+ ext?.onActed?.();
1397
+ }).catch(() => {
1398
+ showFlash(`\u2717 Could not remove you from #${item.number}`);
1399
+ onRefresh?.();
1400
+ });
1401
+ }
1402
+ });
1403
+ }
1387
1404
  actions.push({
1388
1405
  label: "Unsubscribe",
1389
1406
  hint: "u",
@@ -1568,7 +1585,7 @@ var InboxHeader = ({
1568
1585
  sections,
1569
1586
  login,
1570
1587
  brand,
1571
- work,
1588
+ scopeLabel,
1572
1589
  loading,
1573
1590
  quiet,
1574
1591
  refreshing,
@@ -1579,7 +1596,7 @@ var InboxHeader = ({
1579
1596
  const total = sections.reduce((n, s) => n + topLevelCount(s), 0);
1580
1597
  const countSeg = loading ? " loading\u2026 " : quiet ? " " : ` ${String(total).padStart(3)} item${total !== 1 ? "s" : ""} \xB7 `;
1581
1598
  const userSeg = loading || quiet ? "" : `@${login} `;
1582
- const workLabel = work === void 0 ? "" : ` ${work ? "work" : "home"} `;
1599
+ const workLabel = scopeLabel ? ` ${scopeLabel} ` : "";
1583
1600
  const [statusText, statusColor] = hasPending ? [`\u25CF ${pendingSummary || "new"} \xB7 r apply`, "#FF8700"] : refreshing ? ["\u21BB refreshing\u2026", "cyan"] : fetchedAt ? [`updated ${agoText(fetchedAt)}`, void 0] : ["", void 0];
1584
1601
  const statusSeg = statusText ? statusText + " " : "";
1585
1602
  const fill = Math.max(
@@ -1590,7 +1607,7 @@ var InboxHeader = ({
1590
1607
  /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: brand }),
1591
1608
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: countSeg }),
1592
1609
  userSeg ? /* @__PURE__ */ jsx(Text2, { children: userSeg }) : null,
1593
- work !== void 0 ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: workLabel }) : null,
1610
+ scopeLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: workLabel }) : null,
1594
1611
  statusText ? /* @__PURE__ */ jsx(
1595
1612
  Text2,
1596
1613
  {
@@ -1668,6 +1685,17 @@ var TRANSIT_LABEL = {
1668
1685
  };
1669
1686
  var TAB_MARK = "\u25CF";
1670
1687
  var tabLabel = (label, marked, id) => marked.size === 0 ? label : `${marked.has(id) ? TAB_MARK : " "} ${label}`;
1688
+ var repeatedTaskRows = (items) => {
1689
+ const seen = /* @__PURE__ */ new Set();
1690
+ const repeats = /* @__PURE__ */ new Set();
1691
+ items.forEach((row, i) => {
1692
+ if (row.kind !== "task" || !row.url) return;
1693
+ if (seen.has(row.url)) repeats.add(i);
1694
+ else seen.add(row.url);
1695
+ });
1696
+ return repeats;
1697
+ };
1698
+ var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
1671
1699
  var ItemRow = ({
1672
1700
  item,
1673
1701
  active,
@@ -1675,6 +1703,9 @@ var ItemRow = ({
1675
1703
  login,
1676
1704
  merged,
1677
1705
  transient,
1706
+ lastChild,
1707
+ parent,
1708
+ repeat,
1678
1709
  sparkFrame = 0
1679
1710
  }) => {
1680
1711
  if (item.kind === "repo-header")
@@ -1709,8 +1740,9 @@ var ItemRow = ({
1709
1740
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1710
1741
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1711
1742
  /* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1712
- /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
1713
- /* @__PURE__ */ jsx(
1743
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
1744
+ /* @__PURE__ */ jsx(Text2, { color: repeat ? void 0 : "#FF8700", dimColor: repeat, bold: active, children: item.key + " " }),
1745
+ repeat ? null : /* @__PURE__ */ jsx(
1714
1746
  Text2,
1715
1747
  {
1716
1748
  bold: active || transient === "in",
@@ -1719,7 +1751,7 @@ var ItemRow = ({
1719
1751
  children: truncate(item.summary, titleMax2)
1720
1752
  }
1721
1753
  ),
1722
- note ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
1754
+ note && !repeat ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
1723
1755
  transitLabel2 ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel2 }) : null
1724
1756
  ] });
1725
1757
  }
@@ -1746,7 +1778,7 @@ var ItemRow = ({
1746
1778
  const titleMax = Math.max(20, COLS - fixedWidth);
1747
1779
  return /* @__PURE__ */ jsxs(Box, { children: [
1748
1780
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1749
- item.indent ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }) : null,
1781
+ item.indent ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: lastChild === false ? "\u251C\u2500 " : "\u2514\u2500 " }) : null,
1750
1782
  /* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
1751
1783
  /* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1752
1784
  /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
@@ -1901,6 +1933,7 @@ var HelpModal = ({
1901
1933
  ["j", "open repo in new tab"],
1902
1934
  ["p", "open repo in new pane"],
1903
1935
  ["u", "unsubscribe from this item"],
1936
+ ["x", "remove yourself as reviewer"],
1904
1937
  ...hasJira ? [["t", "Jira: move / open ticket"]] : [],
1905
1938
  ["/", "search"],
1906
1939
  ["f", "filter by repo"],
@@ -2089,15 +2122,14 @@ var BrowseScreen = ({
2089
2122
  ciStatusState,
2090
2123
  ciJob,
2091
2124
  tabHelp,
2092
- isWorkRepo,
2093
- includeWork,
2125
+ origin,
2094
2126
  brand,
2095
2127
  mergedUrls,
2096
2128
  transients,
2097
2129
  onTabChange
2098
2130
  }) => {
2099
2131
  const { rows } = useWindowSize();
2100
- const applyWork = (secs) => includeWork === void 0 || !isWorkRepo ? secs : filterByOrigin(secs, includeWork ? "work" : "home", isWorkRepo);
2132
+ const applyWork = (secs) => origin ? filterByOrigin(secs, origin.show, origin.match) : secs;
2101
2133
  const initialSections = applyWork(sections);
2102
2134
  const [localSections, setLocalSections] = useState(initialSections);
2103
2135
  const [tabIdx, setTabIdx] = useState(0);
@@ -2177,6 +2209,10 @@ var BrowseScreen = ({
2177
2209
  { vimKeys: false, isActive: repoPicker }
2178
2210
  );
2179
2211
  const cursor = cursors[activeId] ?? 0;
2212
+ const repeatedTasks = useMemo(
2213
+ () => repeatedTaskRows(section.items),
2214
+ [section.items]
2215
+ );
2180
2216
  const viewStart = viewStarts[activeId] ?? 0;
2181
2217
  const visibleCount = windowCount(section.items, viewStart, listHeight);
2182
2218
  const visibleItems = section.items.slice(viewStart, viewStart + visibleCount);
@@ -2423,6 +2459,18 @@ var BrowseScreen = ({
2423
2459
  void unsubscribeFrom(activeItem).then(() => showFlash(`\u2713 Unsubscribed from #${activeItem.number}`)).catch(() => showFlash(`\u2717 Unsubscribe failed for #${activeItem.number}`));
2424
2460
  return;
2425
2461
  }
2462
+ if (input === "x" && activeItem.kind === "pr" && activeItem.standing === "queued" && login) {
2463
+ removeItemFromSections(activeItem);
2464
+ showFlash(`\u22EF Removing you from #${activeItem.number}\u2026`);
2465
+ void quietly`gh pr edit ${activeItem.number} --repo ${activeItem.repo} --remove-reviewer ${login}`.then(() => {
2466
+ showFlash(`\u2713 Removed you as reviewer on #${activeItem.number}`);
2467
+ onActed?.();
2468
+ }).catch(() => {
2469
+ showFlash(`\u2717 Could not remove you from #${activeItem.number}`);
2470
+ onRefresh?.();
2471
+ });
2472
+ return;
2473
+ }
2426
2474
  if (input === "b" && activeItem.kind === "pr" && activeItem.branch) {
2427
2475
  clipboard(activeItem.branch);
2428
2476
  showFlash(`\u2713 Copied ${activeItem.branch}`);
@@ -2535,7 +2583,7 @@ var BrowseScreen = ({
2535
2583
  brand,
2536
2584
  sections: localSections,
2537
2585
  login,
2538
- work: includeWork,
2586
+ scopeLabel: origin?.label,
2539
2587
  refreshing,
2540
2588
  hasPending,
2541
2589
  pendingSummary,
@@ -2574,6 +2622,9 @@ var BrowseScreen = ({
2574
2622
  login,
2575
2623
  merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2576
2624
  transient: transientOf(transients, item),
2625
+ lastChild: "indent" in item && item.indent ? !isChildRow(section.items[viewStart + i + 1]) : void 0,
2626
+ parent: item.kind === "task" && isChildRow(section.items[viewStart + i + 1]),
2627
+ repeat: repeatedTasks.has(viewStart + i),
2577
2628
  sparkFrame,
2578
2629
  gap: (
2579
2630
  // Window-relative, never `viewStart + i`. fitCount prices the
@@ -2640,14 +2691,17 @@ var NoRowsScreen = ({
2640
2691
  ) })
2641
2692
  ] });
2642
2693
  };
2643
- var signatureOf = (sections) => JSON.stringify(sections, (k, v) => k === "age" ? void 0 : v);
2694
+ var RELATIVE_TIME_KEYS = /* @__PURE__ */ new Set(["age", "activityAge"]);
2695
+ var signatureOf = (sections) => JSON.stringify(
2696
+ sections,
2697
+ (k, v) => RELATIVE_TIME_KEYS.has(k) ? void 0 : v
2698
+ );
2644
2699
  var App = ({
2645
2700
  fetcher,
2646
2701
  cacheKey,
2647
2702
  title = "inbox",
2648
2703
  detailFor,
2649
- isWorkRepo,
2650
- includeWork,
2704
+ origin,
2651
2705
  jiraBase,
2652
2706
  jiraKeyRe,
2653
2707
  jiraTransitions,
@@ -2762,7 +2816,13 @@ var App = ({
2762
2816
  }
2763
2817
  showData(fresh.sections, fresh.login);
2764
2818
  } else if (freshKey !== displayedKey.current) {
2765
- setPending(fresh);
2819
+ const { counts } = diffSections(
2820
+ displayedSections.current,
2821
+ fresh.sections
2822
+ );
2823
+ if (counts.added + counts.removed + counts.changed === 0)
2824
+ showData(fresh.sections, fresh.login);
2825
+ else setPending(fresh);
2766
2826
  } else {
2767
2827
  setPending(null);
2768
2828
  }
@@ -2859,7 +2919,7 @@ var App = ({
2859
2919
  brand: brandOf(title),
2860
2920
  sections: [],
2861
2921
  login: "",
2862
- work: state.phase === "loading" ? includeWork : void 0,
2922
+ scopeLabel: state.phase === "loading" ? origin?.label : void 0,
2863
2923
  loading: state.phase === "loading",
2864
2924
  quiet: state.phase !== "loading"
2865
2925
  }
@@ -2918,8 +2978,7 @@ var App = ({
2918
2978
  pendingSummary,
2919
2979
  fetchedAt,
2920
2980
  refreshError: refreshError ?? void 0,
2921
- isWorkRepo,
2922
- includeWork,
2981
+ origin,
2923
2982
  hidden: overlay !== null,
2924
2983
  mergedUrls,
2925
2984
  transients,
@@ -2976,4 +3035,4 @@ var matchesFilter = (repo, filter) => {
2976
3035
  };
2977
3036
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
2978
3037
 
2979
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
3038
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repeatedTaskRows, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.22.1",
3
+ "version": "0.23.1",
4
4
  "description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",