@kud/gh-ink 0.23.2 → 0.24.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 +17 -18
- package/dist/index.js +35 -41
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -199,8 +199,8 @@ declare const sortItems: (items: GHItem[]) => GHItem[];
|
|
|
199
199
|
declare const sortByRecency: (items: GHItem[]) => GHItem[];
|
|
200
200
|
declare const insertRepoHeaders: (items: GHItem[]) => AnyItem[];
|
|
201
201
|
type Standing = "authored" | "queued" | "spoken";
|
|
202
|
-
declare const whoseMove: (health: Health, sectionId: string, standing?: Standing) => "you" | "them";
|
|
203
|
-
declare const layoutGHItems: (items: GHItem[], sectionId: string) => AnyItem[];
|
|
202
|
+
declare const whoseMove: (health: Health, sectionId: string, standing?: Standing, theySpokeLast?: boolean) => "you" | "them";
|
|
203
|
+
declare const layoutGHItems: (items: GHItem[], sectionId: string, login?: string) => AnyItem[];
|
|
204
204
|
/**
|
|
205
205
|
* A two-way repo split, when a host has one. `undefined` — the ordinary case —
|
|
206
206
|
* means no split at all: every row stands.
|
|
@@ -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,
|
|
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
|
@@ -919,18 +919,38 @@ var YOURS = {
|
|
|
919
919
|
queued: ["waiting", "pending", "threads", "approved"],
|
|
920
920
|
spoken: ["threads"]
|
|
921
921
|
};
|
|
922
|
-
var whoseMove = (health, sectionId, standing) =>
|
|
922
|
+
var whoseMove = (health, sectionId, standing, theySpokeLast) => (
|
|
923
|
+
// Somebody else having the last word outranks every review state below. It is
|
|
924
|
+
// the plainest claim on you there is — a question asked, an objection raised,
|
|
925
|
+
// a "can you rebase" — and none of it shows up as a health, because a bare
|
|
926
|
+
// comment approves nothing, fails nothing and opens no thread.
|
|
927
|
+
//
|
|
928
|
+
// The row already SAID this and the band disagreed with it: the turn arrow
|
|
929
|
+
// reads lastActor and drew `←`, and the explain panel spelled out "X spoke
|
|
930
|
+
// last, your reply is owed", while the band filed it under Their move. Two
|
|
931
|
+
// signals on one row, pointing opposite ways, until 2026-08-27.
|
|
932
|
+
//
|
|
933
|
+
// Only in that direction. YOU having spoken last does not hand the row over —
|
|
934
|
+
// red CI on your own PR is yours whether or not you commented after it — so
|
|
935
|
+
// that case falls through to the table.
|
|
936
|
+
theySpokeLast ? "you" : YOURS[standing ?? STANDING[sectionId] ?? "queued"].includes(health) ? "you" : "them"
|
|
937
|
+
);
|
|
923
938
|
var BAND_LABEL = {
|
|
924
939
|
you: "Your move",
|
|
925
940
|
them: "Their move"
|
|
926
941
|
};
|
|
927
|
-
var layoutGHItems = (items, sectionId) => {
|
|
942
|
+
var layoutGHItems = (items, sectionId, login) => {
|
|
928
943
|
if (sectionId === "done") return insertRepoHeaders(sortByRecency(items));
|
|
929
944
|
const sorted = sortItems(items);
|
|
930
945
|
const bands = ["you", "them"].map((side) => ({
|
|
931
946
|
side,
|
|
932
947
|
rows: sorted.filter(
|
|
933
|
-
(i) => whoseMove(
|
|
948
|
+
(i) => whoseMove(
|
|
949
|
+
i.health,
|
|
950
|
+
sectionId,
|
|
951
|
+
i.standing,
|
|
952
|
+
!!login && !!i.lastActor && i.lastActor !== login
|
|
953
|
+
) === side
|
|
934
954
|
)
|
|
935
955
|
}));
|
|
936
956
|
const filled = bands.filter((b) => b.rows.length > 0);
|
|
@@ -1028,12 +1048,17 @@ var moveCursor = (items, current2, dir) => {
|
|
|
1028
1048
|
if (next < 0 || next >= items.length) return current2;
|
|
1029
1049
|
return next;
|
|
1030
1050
|
};
|
|
1031
|
-
var
|
|
1051
|
+
var gapsAbove = (items, i) => {
|
|
1052
|
+
const item = items[i];
|
|
1053
|
+
if (!item || i === 0) return false;
|
|
1054
|
+
if (item.kind === "repo-header" || item.kind === "subgroup-header") return true;
|
|
1055
|
+
return item.kind === "task";
|
|
1056
|
+
};
|
|
1032
1057
|
var fitCount = (items, start, budget) => {
|
|
1033
1058
|
let lines = 0;
|
|
1034
1059
|
let count = 0;
|
|
1035
1060
|
for (let i = start; i < items.length; i++) {
|
|
1036
|
-
const cost =
|
|
1061
|
+
const cost = i !== start && gapsAbove(items, i) ? 2 : 1;
|
|
1037
1062
|
if (lines + cost > budget) break;
|
|
1038
1063
|
lines += cost;
|
|
1039
1064
|
count++;
|
|
@@ -1685,16 +1710,6 @@ var TRANSIT_LABEL = {
|
|
|
1685
1710
|
};
|
|
1686
1711
|
var TAB_MARK = "\u25CF";
|
|
1687
1712
|
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
1713
|
var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
|
|
1699
1714
|
var ItemRow = ({
|
|
1700
1715
|
item,
|
|
@@ -1705,7 +1720,6 @@ var ItemRow = ({
|
|
|
1705
1720
|
transient,
|
|
1706
1721
|
lastChild,
|
|
1707
1722
|
parent,
|
|
1708
|
-
repeat,
|
|
1709
1723
|
sparkFrame = 0
|
|
1710
1724
|
}) => {
|
|
1711
1725
|
if (item.kind === "repo-header")
|
|
@@ -1741,8 +1755,8 @@ var ItemRow = ({
|
|
|
1741
1755
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1742
1756
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
1743
1757
|
/* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
1744
|
-
/* @__PURE__ */ jsx(Text2, { color:
|
|
1745
|
-
|
|
1758
|
+
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
|
|
1759
|
+
/* @__PURE__ */ jsx(
|
|
1746
1760
|
Text2,
|
|
1747
1761
|
{
|
|
1748
1762
|
bold: active || transient === "in",
|
|
@@ -1751,7 +1765,7 @@ var ItemRow = ({
|
|
|
1751
1765
|
children: truncate(item.summary, titleMax2)
|
|
1752
1766
|
}
|
|
1753
1767
|
),
|
|
1754
|
-
note
|
|
1768
|
+
note ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null,
|
|
1755
1769
|
transitLabel2 ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel2 }) : null
|
|
1756
1770
|
] });
|
|
1757
1771
|
}
|
|
@@ -2209,10 +2223,6 @@ var BrowseScreen = ({
|
|
|
2209
2223
|
{ vimKeys: false, isActive: repoPicker }
|
|
2210
2224
|
);
|
|
2211
2225
|
const cursor = cursors[activeId] ?? 0;
|
|
2212
|
-
const repeatedTasks = useMemo(
|
|
2213
|
-
() => repeatedTaskRows(section.items),
|
|
2214
|
-
[section.items]
|
|
2215
|
-
);
|
|
2216
2226
|
const viewStart = viewStarts[activeId] ?? 0;
|
|
2217
2227
|
const visibleCount = windowCount(section.items, viewStart, listHeight);
|
|
2218
2228
|
const visibleItems = section.items.slice(viewStart, viewStart + visibleCount);
|
|
@@ -2624,24 +2634,8 @@ var BrowseScreen = ({
|
|
|
2624
2634
|
transient: transientOf(transients, item),
|
|
2625
2635
|
lastChild: "indent" in item && item.indent ? !isChildRow(section.items[viewStart + i + 1]) : void 0,
|
|
2626
2636
|
parent: item.kind === "task" && isChildRow(section.items[viewStart + i + 1]),
|
|
2627
|
-
repeat: repeatedTasks.has(viewStart + i),
|
|
2628
2637
|
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
|
-
)
|
|
2638
|
+
gap: i > 0 && gapsAbove(section.items, viewStart + i)
|
|
2645
2639
|
},
|
|
2646
2640
|
`${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
2641
|
)) }),
|
|
@@ -3035,4 +3029,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
3035
3029
|
};
|
|
3036
3030
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
3037
3031
|
|
|
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,
|
|
3032
|
+
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.
|
|
3
|
+
"version": "0.24.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",
|