@kud/gh-ink 0.23.2 → 0.23.3

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
@@ -238,6 +238,20 @@ declare const filterByRepos: (sections: Section[], repos: Set<string>) => Sectio
238
238
  declare const withoutItem: (sections: Section[], target: GHItem) => Section[];
239
239
  declare const reposInSections: (sections: Section[]) => string[];
240
240
  declare const moveCursor: (items: AnyItem[], current: number, dir: 1 | -1) => number;
241
+ /**
242
+ * Whether this row draws a blank line above it.
243
+ *
244
+ * ONE definition, because two readers need the same answer: the renderer draws
245
+ * the gap, and fitCount pays for it out of the window budget. They disagreed
246
+ * until 2026-08-27 — fitCount priced only headers at two lines while the
247
+ * renderer ALSO gapped a task row following a header — so a tab with that shape
248
+ * drew one line more than the window had bought. The frame is sized to fill the
249
+ * terminal exactly, so the overflow scrolls the whole panel instead of clipping.
250
+ *
251
+ * Index-based rather than item-based: whether a row gaps depends on what sits
252
+ * above it, which an item alone cannot answer.
253
+ */
254
+ declare const gapsAbove: (items: readonly AnyItem[], i: number) => boolean;
241
255
  declare const fitCount: (items: AnyItem[], start: number, budget: number) => number;
242
256
  declare const windowCount: (items: AnyItem[], start: number, budget: number) => number;
243
257
  declare const maxViewStart: (items: AnyItem[], budget: number) => number;
@@ -282,21 +296,6 @@ declare const CiStatusLine: ({ state, job, }: {
282
296
  state: CiStatusState;
283
297
  job?: string;
284
298
  }) => 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>;
300
299
  declare const useActionMenu: () => {
301
300
  actions: Action[] | null;
302
301
  cursor: number;
@@ -444,4 +443,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
444
443
  */
445
444
  declare const parsePatterns: (value: string) => string[];
446
445
 
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 };
446
+ 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, gapsAbove, 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, signatureOf, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
package/dist/index.js CHANGED
@@ -1028,12 +1028,17 @@ var moveCursor = (items, current2, dir) => {
1028
1028
  if (next < 0 || next >= items.length) return current2;
1029
1029
  return next;
1030
1030
  };
1031
- var itemLines = (item, isFirst) => (item.kind === "repo-header" || item.kind === "subgroup-header") && !isFirst ? 2 : 1;
1031
+ var gapsAbove = (items, i) => {
1032
+ const item = items[i];
1033
+ if (!item || i === 0) return false;
1034
+ if (item.kind === "repo-header" || item.kind === "subgroup-header") return true;
1035
+ return item.kind === "task";
1036
+ };
1032
1037
  var fitCount = (items, start, budget) => {
1033
1038
  let lines = 0;
1034
1039
  let count = 0;
1035
1040
  for (let i = start; i < items.length; i++) {
1036
- const cost = itemLines(items[i], i === start);
1041
+ const cost = i !== start && gapsAbove(items, i) ? 2 : 1;
1037
1042
  if (lines + cost > budget) break;
1038
1043
  lines += cost;
1039
1044
  count++;
@@ -1685,16 +1690,6 @@ var TRANSIT_LABEL = {
1685
1690
  };
1686
1691
  var TAB_MARK = "\u25CF";
1687
1692
  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
1693
  var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
1699
1694
  var ItemRow = ({
1700
1695
  item,
@@ -1705,7 +1700,6 @@ var ItemRow = ({
1705
1700
  transient,
1706
1701
  lastChild,
1707
1702
  parent,
1708
- repeat,
1709
1703
  sparkFrame = 0
1710
1704
  }) => {
1711
1705
  if (item.kind === "repo-header")
@@ -1741,8 +1735,8 @@ var ItemRow = ({
1741
1735
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1742
1736
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
1743
1737
  /* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1744
- /* @__PURE__ */ jsx(Text2, { color: repeat ? void 0 : "#FF8700", dimColor: repeat, bold: active, children: item.key + " " }),
1745
- repeat ? null : /* @__PURE__ */ jsx(
1738
+ /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
1739
+ /* @__PURE__ */ jsx(
1746
1740
  Text2,
1747
1741
  {
1748
1742
  bold: active || transient === "in",
@@ -1751,7 +1745,7 @@ var ItemRow = ({
1751
1745
  children: truncate(item.summary, titleMax2)
1752
1746
  }
1753
1747
  ),
1754
- note && !repeat ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
1748
+ note ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
1755
1749
  transitLabel2 ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel2 }) : null
1756
1750
  ] });
1757
1751
  }
@@ -2209,10 +2203,6 @@ var BrowseScreen = ({
2209
2203
  { vimKeys: false, isActive: repoPicker }
2210
2204
  );
2211
2205
  const cursor = cursors[activeId] ?? 0;
2212
- const repeatedTasks = useMemo(
2213
- () => repeatedTaskRows(section.items),
2214
- [section.items]
2215
- );
2216
2206
  const viewStart = viewStarts[activeId] ?? 0;
2217
2207
  const visibleCount = windowCount(section.items, viewStart, listHeight);
2218
2208
  const visibleItems = section.items.slice(viewStart, viewStart + visibleCount);
@@ -2624,24 +2614,8 @@ var BrowseScreen = ({
2624
2614
  transient: transientOf(transients, item),
2625
2615
  lastChild: "indent" in item && item.indent ? !isChildRow(section.items[viewStart + i + 1]) : void 0,
2626
2616
  parent: item.kind === "task" && isChildRow(section.items[viewStart + i + 1]),
2627
- repeat: repeatedTasks.has(viewStart + i),
2628
2617
  sparkFrame,
2629
- gap: (
2630
- // Window-relative, never `viewStart + i`. fitCount prices the
2631
- // window's FIRST row at one line (isFirst), so gapping it when
2632
- // scrolled draws a row the budget never bought — and the frame
2633
- // is sized to fill the terminal exactly, so the overflow scrolls
2634
- // the whole panel up a line instead of clipping.
2635
- i > 0 && (item.kind === "repo-header" || item.kind === "subgroup-header" || // A header is always followed by a blank line before its
2636
- // first child — in Other PRs that's "free" because the
2637
- // child is itself a repo-header (gap above). A task row
2638
- // has no such stand-in, so it needs this explicitly. Never
2639
- // applies between two tickets/PRs — only right after a
2640
- // header.
2641
- item.kind === "task" && ["repo-header", "subgroup-header"].includes(
2642
- section.items[viewStart + i - 1]?.kind
2643
- ))
2644
- )
2618
+ gap: i > 0 && gapsAbove(section.items, viewStart + i)
2645
2619
  },
2646
2620
  `${viewStart + i}:${item.kind === "task" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
2647
2621
  )) }),
@@ -3035,4 +3009,4 @@ var matchesFilter = (repo, filter) => {
3035
3009
  };
3036
3010
  var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
3037
3011
 
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 };
3012
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, 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, 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.23.2",
3
+ "version": "0.23.3",
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",