patchrelay 0.21.2 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.21.2",
4
- "commit": "19bd5f240e29",
5
- "builtAt": "2026-03-26T10:43:19.246Z"
3
+ "version": "0.23.1",
4
+ "commit": "b04a5fd406df",
5
+ "builtAt": "2026-03-26T12:04:01.904Z"
6
6
  }
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useReducer } from "react";
2
3
  import { Box, Text, useStdout } from "ink";
3
4
  import { IssueRow } from "./IssueRow.js";
4
5
  import { StatusBar } from "./StatusBar.js";
5
6
  import { HelpBar } from "./HelpBar.js";
6
- const FIXED_COLS = 51;
7
+ // selector(2) + key(10) + status(13) + pr(7) + ago(4) + gaps = ~36
8
+ const FIXED_COLS = 40;
7
9
  const CHROME_ROWS = 4;
8
10
  export function IssueListView({ issues, allIssues, selectedIndex, connected, filter, totalCount }) {
9
11
  const { stdout } = useStdout();
@@ -11,6 +13,9 @@ export function IssueListView({ issues, allIssues, selectedIndex, connected, fil
11
13
  const rows = stdout?.rows ?? 24;
12
14
  const titleWidth = Math.max(0, cols - FIXED_COLS);
13
15
  const maxVisible = Math.max(1, rows - CHROME_ROWS);
16
+ // Periodic refresh for elapsed times
17
+ const [, tick] = useReducer((c) => c + 1, 0);
18
+ useEffect(() => { const id = setInterval(tick, 5000); return () => clearInterval(id); }, []);
14
19
  let startIndex = 0;
15
20
  if (issues.length > maxVisible) {
16
21
  startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), issues.length - maxVisible));
@@ -83,13 +83,25 @@ function truncate(text, max) {
83
83
  return "";
84
84
  return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
85
85
  }
86
+ const TERMINAL_STATES = new Set(["done", "failed", "escalated", "awaiting_input"]);
87
+ function formatStatus(issue) {
88
+ const state = STATE_SHORT[issue.factoryState] ?? issue.factoryState;
89
+ // Terminal states: just the label, no run symbol
90
+ if (TERMINAL_STATES.has(issue.factoryState))
91
+ return state;
92
+ // Active/in-progress: show run status symbol
93
+ const status = issue.activeRunType ? "running" : issue.latestRunStatus;
94
+ const statusSym = status ? (STATUS_SHORT[status] ?? "") : "";
95
+ if (statusSym)
96
+ return `${state} ${statusSym}`;
97
+ return state;
98
+ }
86
99
  export function IssueRow({ issue, selected, titleWidth }) {
87
100
  const key = issue.issueKey ?? issue.projectId;
88
- const state = STATE_SHORT[issue.factoryState] ?? issue.factoryState;
89
- const run = formatRun(issue);
101
+ const status = formatStatus(issue);
90
102
  const pr = formatPr(issue);
91
103
  const ago = relativeTime(issue.updatedAt);
92
104
  const tw = titleWidth ?? 30;
93
105
  const title = issue.title ? truncate(issue.title, tw) : "";
94
- return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "white", bold: selected, children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key.padEnd(9)}` }), _jsx(Text, { color: stateColor(issue.factoryState), children: ` ${state.padEnd(10)}` }), _jsx(Text, { dimColor: true, children: ` ${run.padEnd(10)}` }), _jsx(Text, { dimColor: true, children: ` ${pr.padEnd(6)}` }), _jsx(Text, { dimColor: true, children: ` ${ago.padStart(3)}` }), title ? _jsx(Text, { dimColor: true, children: ` ${title}` }) : null] }));
106
+ return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "white", bold: selected, children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key.padEnd(9)}` }), _jsx(Text, { color: stateColor(issue.factoryState), children: ` ${status.padEnd(12)}` }), _jsx(Text, { dimColor: true, children: ` ${pr.padEnd(6)}` }), _jsx(Text, { dimColor: true, children: ` ${ago.padStart(3)}` }), title ? _jsx(Text, { dimColor: true, children: ` ${title}` }) : null] }));
95
107
  }
@@ -1,18 +1,30 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useStdout } from "ink";
2
+ import { useMemo } from "react";
3
+ import { Box, Static, Text, useStdout } from "ink";
3
4
  import { TimelineRow } from "./TimelineRow.js";
4
- const DETAIL_CHROME_ROWS = 10;
5
+ const ACTIVE_TAIL = 8;
6
+ function isFinalized(entry) {
7
+ if (entry.kind === "item" && entry.item?.status === "inProgress")
8
+ return false;
9
+ if (entry.kind === "run-start")
10
+ return false; // keep run-start in active area until run ends
11
+ return true;
12
+ }
5
13
  export function Timeline({ entries, follow }) {
6
14
  const { stdout } = useStdout();
7
15
  const rows = stdout?.rows ?? 24;
8
- const maxVisible = Math.max(5, rows - DETAIL_CHROME_ROWS);
9
- // Follow mode: tail to screen height. Off: show everything.
10
- const visible = follow && entries.length > maxVisible
11
- ? entries.slice(-maxVisible)
12
- : entries;
13
- const skipped = entries.length - visible.length;
16
+ const maxActive = Math.max(ACTIVE_TAIL, rows - 12);
17
+ // Split: finalized entries go to Static (terminal scrollback), active entries re-render
18
+ const splitIndex = useMemo(() => {
19
+ if (!follow)
20
+ return 0; // follow OFF: everything in active area (re-renders)
21
+ // Find the boundary: keep the last maxActive entries in the active area
22
+ return Math.max(0, entries.length - maxActive);
23
+ }, [entries.length, follow, maxActive]);
24
+ const finalized = entries.slice(0, splitIndex);
25
+ const active = entries.slice(splitIndex);
14
26
  if (entries.length === 0) {
15
27
  return _jsx(Text, { dimColor: true, children: "No timeline events yet." });
16
28
  }
17
- return (_jsxs(Box, { flexDirection: "column", children: [skipped > 0 && _jsxs(Text, { dimColor: true, children: [" ... ", skipped, " earlier"] }), visible.map((entry) => (_jsx(TimelineRow, { entry: entry }, entry.id)))] }));
29
+ return (_jsxs(Box, { flexDirection: "column", children: [finalized.length > 0 && (_jsx(Static, { items: finalized, children: (entry) => _jsx(TimelineRow, { entry: entry }, entry.id) })), active.map((entry) => (_jsx(TimelineRow, { entry: entry }, entry.id)))] }));
18
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.21.2",
3
+ "version": "0.23.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {