patchrelay 0.41.4 → 0.41.6
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/build-info.json
CHANGED
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useReducer } from "react";
|
|
3
3
|
import { Box, Text, useStdout } from "ink";
|
|
4
|
-
import { IssueRow } from "./IssueRow.js";
|
|
4
|
+
import { IssueRow, estimateIssueRowHeight } from "./IssueRow.js";
|
|
5
5
|
import { StatusBar } from "./StatusBar.js";
|
|
6
6
|
import { HelpBar } from "./HelpBar.js";
|
|
7
7
|
const FIXED_COLS = 8;
|
|
8
8
|
const CHROME_ROWS = 4;
|
|
9
|
-
|
|
9
|
+
export function computeVisibleWindow(issues, selectedIndex, maxRows, cols, titleWidth) {
|
|
10
|
+
if (issues.length === 0)
|
|
11
|
+
return { start: 0, end: 0 };
|
|
12
|
+
const clampedSelected = Math.max(0, Math.min(selectedIndex, issues.length - 1));
|
|
13
|
+
const heights = issues.map((issue, index) => estimateIssueRowHeight(issue, index === clampedSelected, cols, titleWidth));
|
|
14
|
+
let start = clampedSelected;
|
|
15
|
+
let end = clampedSelected + 1;
|
|
16
|
+
let usedRows = heights[clampedSelected] ?? 1;
|
|
17
|
+
while (true) {
|
|
18
|
+
const canAddAbove = start > 0 && usedRows + (heights[start - 1] ?? 1) <= maxRows;
|
|
19
|
+
const canAddBelow = end < issues.length && usedRows + (heights[end] ?? 1) <= maxRows;
|
|
20
|
+
if (!canAddAbove && !canAddBelow)
|
|
21
|
+
break;
|
|
22
|
+
const aboveDistance = clampedSelected - start;
|
|
23
|
+
const belowDistance = end - 1 - clampedSelected;
|
|
24
|
+
const preferAbove = canAddAbove && (!canAddBelow || aboveDistance <= belowDistance);
|
|
25
|
+
if (preferAbove) {
|
|
26
|
+
start -= 1;
|
|
27
|
+
usedRows += heights[start] ?? 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (canAddBelow) {
|
|
31
|
+
usedRows += heights[end] ?? 1;
|
|
32
|
+
end += 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { start, end };
|
|
36
|
+
}
|
|
10
37
|
export function IssueListView({ issues, allIssues, selectedIndex, connected, lastServerMessageAt, filter, totalCount, frozen, }) {
|
|
11
38
|
const { stdout } = useStdout();
|
|
12
39
|
const cols = stdout?.columns ?? 80;
|
|
13
40
|
const rows = stdout?.rows ?? 24;
|
|
14
41
|
const titleWidth = Math.max(0, cols - FIXED_COLS);
|
|
15
|
-
const
|
|
42
|
+
const maxVisibleRows = Math.max(1, rows - CHROME_ROWS);
|
|
16
43
|
// Periodic refresh for elapsed times
|
|
17
44
|
const [, tick] = useReducer((c) => c + 1, 0);
|
|
18
45
|
useEffect(() => {
|
|
@@ -21,12 +48,9 @@ export function IssueListView({ issues, allIssues, selectedIndex, connected, las
|
|
|
21
48
|
const id = setInterval(tick, 5000);
|
|
22
49
|
return () => clearInterval(id);
|
|
23
50
|
}, [frozen]);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), issues.length - maxVisible));
|
|
27
|
-
}
|
|
28
|
-
const visible = issues.slice(startIndex, startIndex + maxVisible);
|
|
51
|
+
const { start: startIndex, end: endIndex } = computeVisibleWindow(issues, selectedIndex, maxVisibleRows, cols, titleWidth);
|
|
52
|
+
const visible = issues.slice(startIndex, endIndex);
|
|
29
53
|
const hiddenAbove = startIndex;
|
|
30
|
-
const hiddenBelow = Math.max(0, issues.length -
|
|
54
|
+
const hiddenBelow = Math.max(0, issues.length - endIndex);
|
|
31
55
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { issues: issues, totalCount: totalCount, filter: filter, connected: connected, lastServerMessageAt: lastServerMessageAt, allIssues: allIssues, frozen: frozen ?? false }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: issues.length === 0 ? (_jsx(Text, { dimColor: true, children: "No issues match the current filter." })) : (_jsxs(_Fragment, { children: [hiddenAbove > 0 && _jsxs(Text, { dimColor: true, children: [" ", hiddenAbove, " more above"] }), visible.map((issue, i) => (_jsx(IssueRow, { issue: issue, selected: startIndex + i === selectedIndex, titleWidth: titleWidth }, issue.issueKey ?? `${issue.projectId}-${startIndex + i}`))), hiddenBelow > 0 && _jsxs(Text, { dimColor: true, children: [" ", hiddenBelow, " more below"] })] })) }), _jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "list" }) })] }));
|
|
32
56
|
}
|
|
@@ -3,6 +3,7 @@ import { Box, Text } from "ink";
|
|
|
3
3
|
import { hasOpenPr } from "../../pr-state.js";
|
|
4
4
|
import { summarizeIssueStatusNote } from "./issue-status-note.js";
|
|
5
5
|
import { relativeTime, truncate } from "./format-utils.js";
|
|
6
|
+
import { measureRenderedTextRows } from "./layout-measure.js";
|
|
6
7
|
import { hasDisplayPrBlocker, isApprovedReviewState, isAwaitingReviewState, isChangesRequestedReviewState, isRereviewNeeded, prChecksFact, } from "./pr-status.js";
|
|
7
8
|
// ─── State display ──────────────────────────────────────────────
|
|
8
9
|
const TERMINAL_STATES = new Set(["done", "failed", "escalated"]);
|
|
@@ -100,7 +101,7 @@ function buildFacts(issue, selected) {
|
|
|
100
101
|
facts.push({ text: "awaiting review", color: "yellow" });
|
|
101
102
|
}
|
|
102
103
|
if (issue.factoryState === "awaiting_queue") {
|
|
103
|
-
facts.push({ text: "
|
|
104
|
+
facts.push({ text: "downstream ready", color: "cyan" });
|
|
104
105
|
}
|
|
105
106
|
// Check status — compact
|
|
106
107
|
const checksFact = prChecksFact(issue);
|
|
@@ -167,3 +168,35 @@ export function IssueRow({ issue, selected, titleWidth }) {
|
|
|
167
168
|
}
|
|
168
169
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: detail ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "gray", children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(issue.updatedAt).padStart(4)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color: session.color, children: session.label }), facts.length > 0 && (_jsx(Text, { dimColor: true, children: ` \u00b7 ` })), facts.map((fact, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { dimColor: true, children: ` \u00b7 ` }) : null, _jsx(Text, { color: fact.color ?? "white", dimColor: !fact.color, children: fact.text })] }, i)))] }), title ? (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: title }) })) : null, blocker ? (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "yellow", children: blocker }) })) : null, detail ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: detail }) })) : null, selected && issue.factoryState && issue.sessionState ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, children: `Debug stage: ${stageLabel(issue)}` }) })) : null] }));
|
|
169
170
|
}
|
|
171
|
+
export function estimateIssueRowHeight(issue, selected, cols, titleWidth) {
|
|
172
|
+
const width = Math.max(20, cols);
|
|
173
|
+
const key = issue.issueKey ?? issue.projectId;
|
|
174
|
+
const tw = titleWidth ?? 60;
|
|
175
|
+
const title = issue.title ? truncate(issue.title, tw) : "";
|
|
176
|
+
const detail = selected ? summarizeIssueStatusNote(issue.statusNote) : undefined;
|
|
177
|
+
const session = sessionDisplay(issue);
|
|
178
|
+
const facts = buildFacts(issue, selected);
|
|
179
|
+
const blocker = selected ? blockerText(issue) : null;
|
|
180
|
+
const isTerminal = TERMINAL_STATES.has(effectiveState(issue));
|
|
181
|
+
if (isTerminal && !selected) {
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
const line1Parts = [
|
|
185
|
+
`${selected ? "\u25b8" : " "} ${key}`,
|
|
186
|
+
relativeTime(issue.updatedAt).padStart(4),
|
|
187
|
+
session.label,
|
|
188
|
+
...facts.map((fact) => fact.text),
|
|
189
|
+
];
|
|
190
|
+
let rows = measureRenderedTextRows(line1Parts.join(" · "), width);
|
|
191
|
+
if (title)
|
|
192
|
+
rows += measureRenderedTextRows(title, Math.max(8, width - 2));
|
|
193
|
+
if (blocker)
|
|
194
|
+
rows += measureRenderedTextRows(blocker, Math.max(8, width - 2));
|
|
195
|
+
if (detail)
|
|
196
|
+
rows += measureRenderedTextRows(detail, Math.max(8, width - 4));
|
|
197
|
+
if (selected && issue.factoryState && issue.sessionState)
|
|
198
|
+
rows += 1;
|
|
199
|
+
if (detail)
|
|
200
|
+
rows += 1;
|
|
201
|
+
return Math.max(1, rows);
|
|
202
|
+
}
|
|
@@ -401,7 +401,7 @@ function buildFactSegments(issue, issueContext) {
|
|
|
401
401
|
&& (isAwaitingReviewState(issue.prReviewState) || (!issue.prReviewState && issue.factoryState === "pr_open")))
|
|
402
402
|
facts.push([{ text: "awaiting review", color: "yellow" }]);
|
|
403
403
|
if (issue.factoryState === "awaiting_queue")
|
|
404
|
-
facts.push([{ text: "
|
|
404
|
+
facts.push([{ text: "downstream ready", color: "cyan" }]);
|
|
405
405
|
if (issue.waitingReason && issue.sessionState === "waiting_input")
|
|
406
406
|
facts.push([{ text: issue.waitingReason, color: "yellow" }]);
|
|
407
407
|
const checks = prChecksFact({
|