@d3lm/pr-stats 0.2.1 → 0.2.2
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/README.md +7 -5
- package/dist/tui-app.mjs +478 -245
- package/package.json +28 -20
package/dist/tui-app.mjs
CHANGED
|
@@ -28,9 +28,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
mod
|
|
29
29
|
));
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// node_modules/.pnpm/asciichart@1.5.25/node_modules/asciichart/asciichart.js
|
|
32
32
|
var require_asciichart = __commonJS({
|
|
33
|
-
"
|
|
33
|
+
"node_modules/.pnpm/asciichart@1.5.25/node_modules/asciichart/asciichart.js"(exports) {
|
|
34
34
|
"use strict";
|
|
35
35
|
(function(exports2) {
|
|
36
36
|
exports2.black = "\x1B[30m";
|
|
@@ -130,23 +130,23 @@ var require_asciichart = __commonJS({
|
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
// tui/main.tsx
|
|
133
|
+
// src/tui/main.tsx
|
|
134
134
|
import { createCliRenderer } from "@opentui/core";
|
|
135
135
|
import { createRoot } from "@opentui/react";
|
|
136
136
|
|
|
137
|
-
// tui/App.tsx
|
|
137
|
+
// src/tui/App.tsx
|
|
138
138
|
import { useKeyboard, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
|
|
139
139
|
import { useEffect as useEffect6, useReducer, useRef as useRef5, useState as useState4 } from "react";
|
|
140
140
|
|
|
141
|
-
// settings.ts
|
|
141
|
+
// src/settings.ts
|
|
142
142
|
import { readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
143
143
|
import { join as join2 } from "node:path";
|
|
144
144
|
|
|
145
|
-
// cache.ts
|
|
145
|
+
// src/cache.ts
|
|
146
146
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
147
147
|
import { homedir } from "node:os";
|
|
148
148
|
import { dirname, join } from "node:path";
|
|
149
|
-
var VERSION =
|
|
149
|
+
var VERSION = 2;
|
|
150
150
|
var enabled = false;
|
|
151
151
|
function configureCache(on) {
|
|
152
152
|
enabled = on;
|
|
@@ -263,7 +263,7 @@ var PrCache = class {
|
|
|
263
263
|
}
|
|
264
264
|
};
|
|
265
265
|
|
|
266
|
-
// utils.ts
|
|
266
|
+
// src/utils.ts
|
|
267
267
|
var CliError = class extends Error {
|
|
268
268
|
};
|
|
269
269
|
function fail(message) {
|
|
@@ -278,7 +278,7 @@ function percentile(sorted, percent) {
|
|
|
278
278
|
return sorted[Math.max(0, index)];
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
// settings.ts
|
|
281
|
+
// src/settings.ts
|
|
282
282
|
function settingsFile() {
|
|
283
283
|
return join2(cacheDir(), "settings.json");
|
|
284
284
|
}
|
|
@@ -361,7 +361,7 @@ function applySettings(values, explicit) {
|
|
|
361
361
|
return settings;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
// tui/theme.ts
|
|
364
|
+
// src/tui/theme.ts
|
|
365
365
|
var BASE = {
|
|
366
366
|
bg: "#1e1e1e",
|
|
367
367
|
border: "#363636",
|
|
@@ -547,13 +547,14 @@ function themeColorText(key) {
|
|
|
547
547
|
return Array.isArray(value2) ? value2.join(" ") : value2;
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
// tui/components/Footer.tsx
|
|
550
|
+
// src/tui/components/Footer.tsx
|
|
551
551
|
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
552
552
|
function Footer({
|
|
553
553
|
width,
|
|
554
554
|
modal,
|
|
555
555
|
editing,
|
|
556
556
|
tab,
|
|
557
|
+
authoredTab,
|
|
557
558
|
views,
|
|
558
559
|
copyLinks: copyLinks2,
|
|
559
560
|
openError,
|
|
@@ -563,7 +564,7 @@ function Footer({
|
|
|
563
564
|
const notice = openError ?? copyNotice ?? (stale ? "options changed \xB7 press r to reload" : "");
|
|
564
565
|
const check = openError === null && copyNotice !== null;
|
|
565
566
|
const noticeWidth = notice === "" ? 0 : notice.length + (check ? 2 : 0) + 2;
|
|
566
|
-
const hints = truncated(hintsFor(modal, editing, tab, views, copyLinks2), width - 2 - noticeWidth);
|
|
567
|
+
const hints = truncated(hintsFor(modal, editing, tab, authoredTab, views, copyLinks2), width - 2 - noticeWidth);
|
|
567
568
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
568
569
|
/* @__PURE__ */ jsx("box", { height: 1, children: /* @__PURE__ */ jsx("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
569
570
|
/* @__PURE__ */ jsxs(
|
|
@@ -592,7 +593,7 @@ function truncated(text, limit2) {
|
|
|
592
593
|
}
|
|
593
594
|
return limit2 <= 1 ? "" : `${text.slice(0, limit2 - 1).trimEnd()}\u2026`;
|
|
594
595
|
}
|
|
595
|
-
function hintsFor(modal, editing, tab, views, copyLinks2) {
|
|
596
|
+
function hintsFor(modal, editing, tab, authoredTab, views, copyLinks2) {
|
|
596
597
|
if (modal === "options") {
|
|
597
598
|
return editing ? "enter apply \xB7 esc cancel" : "\u2191/\u2193 select \xB7 enter edit \xB7 \u2190/\u2192 toggle \xB7 s save \xB7 esc close \xB7 q quit";
|
|
598
599
|
}
|
|
@@ -602,30 +603,31 @@ function hintsFor(modal, editing, tab, views, copyLinks2) {
|
|
|
602
603
|
if (modal === "theme") {
|
|
603
604
|
return editing ? "enter apply \xB7 esc cancel" : "\u2191/\u2193 select \xB7 enter edit hex \xB7 esc back \xB7 q quit";
|
|
604
605
|
}
|
|
605
|
-
|
|
606
|
+
const toggle = tab === 1 ? authoredTab === "open" ? "t merged stats \xB7 " : "t open PRs \xB7 " : "";
|
|
607
|
+
if (tab === 0 || tab === 1 && authoredTab === "open") {
|
|
606
608
|
const scope2 = views === null ? null : tab === 0 ? views.pendingScope : views.openScope;
|
|
607
609
|
const repos2 = views === null ? [] : tab === 0 ? views.pendingRepos : views.openRepos;
|
|
608
610
|
if (scope2?.view === "list") {
|
|
609
|
-
return
|
|
611
|
+
return `\u2191/\u2193 select \xB7 enter open \xB7 ${toggle}\u2190/\u2192 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
610
612
|
}
|
|
611
613
|
const action = copyLinks2 ? "enter copy link" : "enter open";
|
|
612
614
|
if (scope2 !== null && repos2.length > 0) {
|
|
613
|
-
return scope2.repo === null ? `\u2191/\u2193 select \xB7 ${action} \xB7 g group by repo \xB7 esc back \xB7 o options \xB7 s settings \xB7 r reload \xB7 q quit` : `\u2191/\u2193 select \xB7 ${action} \xB7 esc back \xB7 1-5 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
615
|
+
return scope2.repo === null ? `\u2191/\u2193 select \xB7 ${action} \xB7 ${toggle}g group by repo \xB7 esc back \xB7 o options \xB7 s settings \xB7 r reload \xB7 q quit` : `\u2191/\u2193 select \xB7 ${action} \xB7 ${toggle}esc back \xB7 1-5 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
614
616
|
}
|
|
615
|
-
return `\u2191/\u2193 select \xB7 ${copyLinks2 ? "enter copy link" : "enter open in browser"} \xB7 \u2190/\u2192 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
617
|
+
return `\u2191/\u2193 select \xB7 ${copyLinks2 ? "enter copy link" : "enter open in browser"} \xB7 ${toggle}\u2190/\u2192 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
616
618
|
}
|
|
617
|
-
const scope = views === null ? null : tab === 2 ? views.reviewScope : tab === 3 ? views.sizeScope : views.commentScope;
|
|
618
|
-
const repos = views === null ? [] : tab === 2 ? views.reviewRepos : tab === 3 ? views.sizeRepos : views.commentRepos;
|
|
619
|
+
const scope = views === null ? null : tab === 1 ? views.mergedScope : tab === 2 ? views.reviewScope : tab === 3 ? views.sizeScope : views.commentScope;
|
|
620
|
+
const repos = views === null ? [] : tab === 1 ? views.mergedRepos : tab === 2 ? views.reviewRepos : tab === 3 ? views.sizeRepos : views.commentRepos;
|
|
619
621
|
if (scope?.view === "list") {
|
|
620
|
-
return
|
|
622
|
+
return `\u2191/\u2193 select \xB7 enter open \xB7 ${toggle}\u2190/\u2192 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
621
623
|
}
|
|
622
624
|
if (scope !== null && repos.length > 0) {
|
|
623
|
-
return
|
|
625
|
+
return `${toggle}esc back \xB7 j/k scroll \xB7 1-5 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
624
626
|
}
|
|
625
|
-
return
|
|
627
|
+
return `${toggle}1-5 tabs \xB7 j/k scroll \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
626
628
|
}
|
|
627
629
|
|
|
628
|
-
// tui/components/Spinner.tsx
|
|
630
|
+
// src/tui/components/Spinner.tsx
|
|
629
631
|
import { useEffect, useState } from "react";
|
|
630
632
|
import { jsx as jsx2 } from "@opentui/react/jsx-runtime";
|
|
631
633
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -643,7 +645,7 @@ function Spinner() {
|
|
|
643
645
|
return /* @__PURE__ */ jsx2("text", { wrapMode: "none", fg: theme.accent, children: SPINNER_FRAMES[frame] });
|
|
644
646
|
}
|
|
645
647
|
|
|
646
|
-
// tui/components/Header.tsx
|
|
648
|
+
// src/tui/components/Header.tsx
|
|
647
649
|
import { jsx as jsx3, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
|
|
648
650
|
function Header({
|
|
649
651
|
options,
|
|
@@ -677,7 +679,7 @@ function timeModeLabel(options) {
|
|
|
677
679
|
return options.workHours === "0-24" ? `Mon-Fri all hours ${tz}` : `Mon-Fri ${options.workHours} ${tz}`;
|
|
678
680
|
}
|
|
679
681
|
|
|
680
|
-
// time.ts
|
|
682
|
+
// src/time.ts
|
|
681
683
|
function wallFormatter(tz) {
|
|
682
684
|
return new Intl.DateTimeFormat("en-US", {
|
|
683
685
|
timeZone: tz,
|
|
@@ -768,7 +770,7 @@ function durationHours(start, end) {
|
|
|
768
770
|
return businessMsBetween(start, end) / 36e5;
|
|
769
771
|
}
|
|
770
772
|
|
|
771
|
-
// compute.ts
|
|
773
|
+
// src/compute.ts
|
|
772
774
|
function computeReviewStats(results, { targetHours, now = /* @__PURE__ */ new Date() } = {}) {
|
|
773
775
|
const reviewed = [];
|
|
774
776
|
const pending = [];
|
|
@@ -818,6 +820,23 @@ function computeSizeStats(sizes, { sizeTarget } = {}) {
|
|
|
818
820
|
const misses = sizes.filter((size) => !meetsTarget(size)).toSorted((a, b) => b.total - a.total);
|
|
819
821
|
return { metrics, timelineTotals, met, misses, targetLabel };
|
|
820
822
|
}
|
|
823
|
+
function computeMergeStats(sizes) {
|
|
824
|
+
const merged = [];
|
|
825
|
+
const closed = [];
|
|
826
|
+
const open = [];
|
|
827
|
+
for (const entry of sizes) {
|
|
828
|
+
if (entry.mergedAt !== null) {
|
|
829
|
+
merged.push({ entry, mergedAt: entry.mergedAt, hours: durationHours(entry.pr.createdAt, entry.mergedAt) });
|
|
830
|
+
} else if (entry.pr.state === "open") {
|
|
831
|
+
open.push(entry);
|
|
832
|
+
} else if (entry.closedAt !== null) {
|
|
833
|
+
closed.push({ entry, closedAt: entry.closedAt, hours: durationHours(entry.pr.createdAt, entry.closedAt) });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
merged.sort((a, b) => b.mergedAt.getTime() - a.mergedAt.getTime());
|
|
837
|
+
closed.sort((a, b) => b.closedAt.getTime() - a.closedAt.getTime());
|
|
838
|
+
return { merged, closed, open, allHours: merged.map((result) => result.hours) };
|
|
839
|
+
}
|
|
821
840
|
function computeCommentStats(sizes) {
|
|
822
841
|
const metrics = [
|
|
823
842
|
{ label: "discussion comments", values: sizes.map((size) => size.comments.discussion) },
|
|
@@ -830,7 +849,7 @@ function computeCommentStats(sizes) {
|
|
|
830
849
|
return { metrics, totals, uncommented, top };
|
|
831
850
|
}
|
|
832
851
|
|
|
833
|
-
// report.ts
|
|
852
|
+
// src/report.ts
|
|
834
853
|
var BUCKETS = [];
|
|
835
854
|
function initBuckets() {
|
|
836
855
|
BUCKETS = makeBuckets();
|
|
@@ -911,7 +930,7 @@ function formatCount(value2) {
|
|
|
911
930
|
return `${scaled >= 10 ? Math.round(scaled) : scaled.toFixed(1)}k`;
|
|
912
931
|
}
|
|
913
932
|
|
|
914
|
-
// tui/views/rows.ts
|
|
933
|
+
// src/tui/views/rows.ts
|
|
915
934
|
function durationLead(result) {
|
|
916
935
|
return `${formatHoursOnly(result.hours).padStart(8)}${weeksSuffix(result.hours)}`;
|
|
917
936
|
}
|
|
@@ -927,7 +946,7 @@ function toPrRows(entries, leads) {
|
|
|
927
946
|
});
|
|
928
947
|
}
|
|
929
948
|
|
|
930
|
-
// tui/views/queue.ts
|
|
949
|
+
// src/tui/views/queue.ts
|
|
931
950
|
function queueRows(view) {
|
|
932
951
|
return view.lists.flatMap((list) => list.rows);
|
|
933
952
|
}
|
|
@@ -986,10 +1005,10 @@ function rowsOf(group) {
|
|
|
986
1005
|
);
|
|
987
1006
|
}
|
|
988
1007
|
|
|
989
|
-
// tui/components/ChartsPanel.tsx
|
|
1008
|
+
// src/tui/components/ChartsPanel.tsx
|
|
990
1009
|
import { useTerminalDimensions } from "@opentui/react";
|
|
991
1010
|
|
|
992
|
-
// tui/hooks/scrollbar.ts
|
|
1011
|
+
// src/tui/hooks/scrollbar.ts
|
|
993
1012
|
import { CliRenderEvents } from "@opentui/core";
|
|
994
1013
|
import { useRenderer } from "@opentui/react";
|
|
995
1014
|
import { useLayoutEffect } from "react";
|
|
@@ -1020,7 +1039,7 @@ function useScrollbarSettle(scrollRef, mounted = true) {
|
|
|
1020
1039
|
}, [scrollRef, renderer2, mounted]);
|
|
1021
1040
|
}
|
|
1022
1041
|
|
|
1023
|
-
// tui/views/charts/model.ts
|
|
1042
|
+
// src/tui/views/charts/model.ts
|
|
1024
1043
|
function lineLength(line) {
|
|
1025
1044
|
return line.reduce((sum, span) => sum + span.text.length, 0);
|
|
1026
1045
|
}
|
|
@@ -1041,7 +1060,7 @@ function compact(value2) {
|
|
|
1041
1060
|
return String(Math.round(value2 * 10) / 10);
|
|
1042
1061
|
}
|
|
1043
1062
|
|
|
1044
|
-
// tui/components/ChartsPanel.tsx
|
|
1063
|
+
// src/tui/components/ChartsPanel.tsx
|
|
1045
1064
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
|
|
1046
1065
|
var COLUMN_GAP = 4;
|
|
1047
1066
|
function ChartsPanel({
|
|
@@ -1146,7 +1165,7 @@ function ChartCard({ card }) {
|
|
|
1146
1165
|
] });
|
|
1147
1166
|
}
|
|
1148
1167
|
|
|
1149
|
-
// tui/components/Placeholder.tsx
|
|
1168
|
+
// src/tui/components/Placeholder.tsx
|
|
1150
1169
|
import { jsx as jsx5, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
|
|
1151
1170
|
var BAR_WIDTH = 40;
|
|
1152
1171
|
var PARTIAL_BLOCKS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
|
|
@@ -1180,7 +1199,7 @@ function loadLabel(load) {
|
|
|
1180
1199
|
return load.phase === "search" ? "searching PRs..." : "fetching PR details";
|
|
1181
1200
|
}
|
|
1182
1201
|
|
|
1183
|
-
// tui/components/QueuePanel.tsx
|
|
1202
|
+
// src/tui/components/QueuePanel.tsx
|
|
1184
1203
|
import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
|
|
1185
1204
|
import { useEffect as useEffect2, useRef } from "react";
|
|
1186
1205
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
|
|
@@ -1280,7 +1299,7 @@ function QueuePanel({
|
|
|
1280
1299
|
] });
|
|
1281
1300
|
}
|
|
1282
1301
|
|
|
1283
|
-
// tui/components/RepoList.tsx
|
|
1302
|
+
// src/tui/components/RepoList.tsx
|
|
1284
1303
|
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
1285
1304
|
import { jsx as jsx7, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
|
|
1286
1305
|
function RepoList({
|
|
@@ -1320,8 +1339,123 @@ function RepoList({
|
|
|
1320
1339
|
);
|
|
1321
1340
|
}
|
|
1322
1341
|
|
|
1323
|
-
// tui/
|
|
1324
|
-
|
|
1342
|
+
// src/tui/state/browse.ts
|
|
1343
|
+
var TABS = ["1 Awaiting you", "2 Your PRs", "3 Review time", "4 PR size", "5 Comments"];
|
|
1344
|
+
var initialBrowseState = {
|
|
1345
|
+
tab: 0,
|
|
1346
|
+
authoredTab: "open",
|
|
1347
|
+
scopes: {
|
|
1348
|
+
pending: { view: "list" },
|
|
1349
|
+
open: { view: "list" },
|
|
1350
|
+
review: { view: "list" },
|
|
1351
|
+
size: { view: "list" },
|
|
1352
|
+
comment: { view: "list" },
|
|
1353
|
+
merged: { view: "list" }
|
|
1354
|
+
},
|
|
1355
|
+
repoCursors: { pending: 0, open: 0, review: 0, size: 0, comment: 0, merged: 0 },
|
|
1356
|
+
rowCursors: { pending: 0, open: 0 },
|
|
1357
|
+
grouped: { pending: false, open: false }
|
|
1358
|
+
};
|
|
1359
|
+
function dropVanishedRepo(scope, repos) {
|
|
1360
|
+
if (scope.view === "detail" && scope.repo !== null && !repos.some((option) => option.repo === scope.repo)) {
|
|
1361
|
+
return { view: "list" };
|
|
1362
|
+
}
|
|
1363
|
+
return scope;
|
|
1364
|
+
}
|
|
1365
|
+
function cycled(previous, delta, count2) {
|
|
1366
|
+
return (Math.min(previous, count2 - 1) + count2 + delta) % count2;
|
|
1367
|
+
}
|
|
1368
|
+
function browseReducer(state, action) {
|
|
1369
|
+
switch (action.type) {
|
|
1370
|
+
case "tabSelected": {
|
|
1371
|
+
return { ...state, tab: action.tab };
|
|
1372
|
+
}
|
|
1373
|
+
case "tabCycled": {
|
|
1374
|
+
return { ...state, tab: (state.tab + TABS.length + action.delta) % TABS.length };
|
|
1375
|
+
}
|
|
1376
|
+
case "subTabToggled": {
|
|
1377
|
+
return { ...state, authoredTab: state.authoredTab === "open" ? "merged" : "open" };
|
|
1378
|
+
}
|
|
1379
|
+
case "repoCursorMoved": {
|
|
1380
|
+
return {
|
|
1381
|
+
...state,
|
|
1382
|
+
repoCursors: {
|
|
1383
|
+
...state.repoCursors,
|
|
1384
|
+
[action.tab]: cycled(state.repoCursors[action.tab], action.delta, action.count)
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
case "rowCursorMoved": {
|
|
1389
|
+
return {
|
|
1390
|
+
...state,
|
|
1391
|
+
rowCursors: {
|
|
1392
|
+
...state.rowCursors,
|
|
1393
|
+
[action.tab]: cycled(state.rowCursors[action.tab], action.delta, action.count)
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
case "repoOpened": {
|
|
1398
|
+
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "detail", repo: action.repo } } };
|
|
1399
|
+
}
|
|
1400
|
+
case "pickerReturned": {
|
|
1401
|
+
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "list" } } };
|
|
1402
|
+
}
|
|
1403
|
+
case "groupingToggled": {
|
|
1404
|
+
return { ...state, grouped: { ...state.grouped, [action.tab]: !state.grouped[action.tab] } };
|
|
1405
|
+
}
|
|
1406
|
+
case "dataLoaded": {
|
|
1407
|
+
return {
|
|
1408
|
+
...state,
|
|
1409
|
+
scopes: {
|
|
1410
|
+
pending: dropVanishedRepo(state.scopes.pending, action.repos.pending),
|
|
1411
|
+
open: dropVanishedRepo(state.scopes.open, action.repos.open),
|
|
1412
|
+
review: dropVanishedRepo(state.scopes.review, action.repos.review),
|
|
1413
|
+
size: dropVanishedRepo(state.scopes.size, action.repos.size),
|
|
1414
|
+
comment: dropVanishedRepo(state.scopes.comment, action.repos.comment),
|
|
1415
|
+
merged: dropVanishedRepo(state.scopes.merged, action.repos.merged)
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return state;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/tui/components/TabBar.tsx
|
|
1424
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
|
|
1425
|
+
function TabBar({ tab }) {
|
|
1426
|
+
return /* @__PURE__ */ jsx8("box", { flexDirection: "row", height: 1, paddingLeft: 1, marginTop: 1, columnGap: 1, children: TABS.map((label, i) => /* @__PURE__ */ jsx8(
|
|
1427
|
+
"text",
|
|
1428
|
+
{
|
|
1429
|
+
wrapMode: "none",
|
|
1430
|
+
fg: i === tab ? theme.accent : theme.muted,
|
|
1431
|
+
bg: i === tab ? theme.selectedBg : void 0,
|
|
1432
|
+
children: ` ${label} `
|
|
1433
|
+
},
|
|
1434
|
+
label
|
|
1435
|
+
)) });
|
|
1436
|
+
}
|
|
1437
|
+
var SUB_TABS = [
|
|
1438
|
+
{ key: "open", label: "Open" },
|
|
1439
|
+
{ key: "merged", label: "Merged & closed" }
|
|
1440
|
+
];
|
|
1441
|
+
function SubTabBar({ active }) {
|
|
1442
|
+
return /* @__PURE__ */ jsxs7("box", { flexDirection: "row", height: 1, paddingLeft: 1, marginBottom: 1, columnGap: 1, children: [
|
|
1443
|
+
SUB_TABS.map(({ key, label }) => /* @__PURE__ */ jsx8(
|
|
1444
|
+
"text",
|
|
1445
|
+
{
|
|
1446
|
+
wrapMode: "none",
|
|
1447
|
+
fg: key === active ? theme.accent : theme.muted,
|
|
1448
|
+
bg: key === active ? theme.selectedBg : void 0,
|
|
1449
|
+
children: ` ${label} `
|
|
1450
|
+
},
|
|
1451
|
+
key
|
|
1452
|
+
)),
|
|
1453
|
+
/* @__PURE__ */ jsx8("text", { wrapMode: "none", fg: theme.dim, children: "t switches" })
|
|
1454
|
+
] });
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// src/tui/components/MainPanel.tsx
|
|
1458
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
|
|
1325
1459
|
function QueueTab({
|
|
1326
1460
|
prompt,
|
|
1327
1461
|
repos,
|
|
@@ -1334,12 +1468,12 @@ function QueueTab({
|
|
|
1334
1468
|
onRefClick
|
|
1335
1469
|
}) {
|
|
1336
1470
|
if (scope.view === "list") {
|
|
1337
|
-
return /* @__PURE__ */
|
|
1471
|
+
return /* @__PURE__ */ jsx9(RepoList, { prompt, options: repos, cursor: Math.min(repoCursor, repos.length - 1) });
|
|
1338
1472
|
}
|
|
1339
1473
|
if (view === null) {
|
|
1340
1474
|
return null;
|
|
1341
1475
|
}
|
|
1342
|
-
return /* @__PURE__ */
|
|
1476
|
+
return /* @__PURE__ */ jsx9(
|
|
1343
1477
|
QueuePanel,
|
|
1344
1478
|
{
|
|
1345
1479
|
heading: repos.length > 0 ? scope.repo ?? (grouped ? "All repos \xB7 grouped by repo" : "All repos") : null,
|
|
@@ -1361,12 +1495,12 @@ function StatsTab({
|
|
|
1361
1495
|
warning
|
|
1362
1496
|
}) {
|
|
1363
1497
|
if (scope.view === "list") {
|
|
1364
|
-
return /* @__PURE__ */
|
|
1498
|
+
return /* @__PURE__ */ jsx9(RepoList, { options: repos, cursor: Math.min(cursor, repos.length - 1) });
|
|
1365
1499
|
}
|
|
1366
1500
|
if (view === null) {
|
|
1367
1501
|
return null;
|
|
1368
1502
|
}
|
|
1369
|
-
return /* @__PURE__ */
|
|
1503
|
+
return /* @__PURE__ */ jsx9(
|
|
1370
1504
|
ChartsPanel,
|
|
1371
1505
|
{
|
|
1372
1506
|
scrollRef,
|
|
@@ -1388,7 +1522,7 @@ function MainPanel({
|
|
|
1388
1522
|
load,
|
|
1389
1523
|
onRefClick
|
|
1390
1524
|
}) {
|
|
1391
|
-
return /* @__PURE__ */
|
|
1525
|
+
return /* @__PURE__ */ jsx9("box", { flexGrow: 1, flexDirection: "column", marginTop: 1, children: views === null ? /* @__PURE__ */ jsx9(Placeholder, { error, loading, load }) : browse.tab === 0 ? /* @__PURE__ */ jsx9(
|
|
1392
1526
|
QueueTab,
|
|
1393
1527
|
{
|
|
1394
1528
|
prompt: "Select a repository and press enter to open its review queue.",
|
|
@@ -1401,20 +1535,34 @@ function MainPanel({
|
|
|
1401
1535
|
warning,
|
|
1402
1536
|
onRefClick
|
|
1403
1537
|
}
|
|
1404
|
-
) : browse.tab === 1 ? /* @__PURE__ */
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1538
|
+
) : browse.tab === 1 ? /* @__PURE__ */ jsxs8("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
1539
|
+
/* @__PURE__ */ jsx9(SubTabBar, { active: browse.authoredTab }),
|
|
1540
|
+
browse.authoredTab === "open" ? /* @__PURE__ */ jsx9(
|
|
1541
|
+
QueueTab,
|
|
1542
|
+
{
|
|
1543
|
+
prompt: "Select a repository and press enter to list its open PRs.",
|
|
1544
|
+
repos: views.openRepos,
|
|
1545
|
+
scope: views.openScope,
|
|
1546
|
+
view: views.open,
|
|
1547
|
+
repoCursor: browse.repoCursors.open,
|
|
1548
|
+
rowCursor: browse.rowCursors.open,
|
|
1549
|
+
grouped: browse.grouped.open,
|
|
1550
|
+
warning,
|
|
1551
|
+
onRefClick
|
|
1552
|
+
}
|
|
1553
|
+
) : /* @__PURE__ */ jsx9(
|
|
1554
|
+
StatsTab,
|
|
1555
|
+
{
|
|
1556
|
+
repos: views.mergedRepos,
|
|
1557
|
+
scope: views.mergedScope,
|
|
1558
|
+
view: views.merged,
|
|
1559
|
+
cursor: browse.repoCursors.merged,
|
|
1560
|
+
scrollRef: scrollRefs.merged,
|
|
1561
|
+
focused,
|
|
1562
|
+
warning
|
|
1563
|
+
}
|
|
1564
|
+
)
|
|
1565
|
+
] }) : browse.tab === 2 ? /* @__PURE__ */ jsx9(
|
|
1418
1566
|
StatsTab,
|
|
1419
1567
|
{
|
|
1420
1568
|
repos: views.reviewRepos,
|
|
@@ -1425,7 +1573,7 @@ function MainPanel({
|
|
|
1425
1573
|
focused,
|
|
1426
1574
|
warning
|
|
1427
1575
|
}
|
|
1428
|
-
) : browse.tab === 3 ? /* @__PURE__ */
|
|
1576
|
+
) : browse.tab === 3 ? /* @__PURE__ */ jsx9(
|
|
1429
1577
|
StatsTab,
|
|
1430
1578
|
{
|
|
1431
1579
|
repos: views.sizeRepos,
|
|
@@ -1436,7 +1584,7 @@ function MainPanel({
|
|
|
1436
1584
|
focused,
|
|
1437
1585
|
warning
|
|
1438
1586
|
}
|
|
1439
|
-
) : /* @__PURE__ */
|
|
1587
|
+
) : /* @__PURE__ */ jsx9(
|
|
1440
1588
|
StatsTab,
|
|
1441
1589
|
{
|
|
1442
1590
|
repos: views.commentRepos,
|
|
@@ -1450,7 +1598,7 @@ function MainPanel({
|
|
|
1450
1598
|
) });
|
|
1451
1599
|
}
|
|
1452
1600
|
|
|
1453
|
-
//
|
|
1601
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/utilities.js
|
|
1454
1602
|
function stringReplaceAll(string, substring, postfix) {
|
|
1455
1603
|
let index = string.indexOf(substring);
|
|
1456
1604
|
if (index === -1) {
|
|
@@ -1480,7 +1628,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
|
1480
1628
|
return returnValue;
|
|
1481
1629
|
}
|
|
1482
1630
|
|
|
1483
|
-
//
|
|
1631
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
1484
1632
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
1485
1633
|
var ANSI_UNDERLINE_OFFSET = 20;
|
|
1486
1634
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
@@ -1702,7 +1850,7 @@ function assembleStyles() {
|
|
|
1702
1850
|
var ansiStyles = assembleStyles();
|
|
1703
1851
|
var ansi_styles_default = ansiStyles;
|
|
1704
1852
|
|
|
1705
|
-
//
|
|
1853
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/supports-color/index.js
|
|
1706
1854
|
import process2 from "node:process";
|
|
1707
1855
|
import os from "node:os";
|
|
1708
1856
|
import tty from "node:tty";
|
|
@@ -1844,7 +1992,7 @@ var supportsColor = {
|
|
|
1844
1992
|
};
|
|
1845
1993
|
var supports_color_default = supportsColor;
|
|
1846
1994
|
|
|
1847
|
-
//
|
|
1995
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/index.js
|
|
1848
1996
|
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
1849
1997
|
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
1850
1998
|
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
@@ -2012,7 +2160,7 @@ var chalk = createChalk();
|
|
|
2012
2160
|
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
2013
2161
|
var source_default = chalk;
|
|
2014
2162
|
|
|
2015
|
-
// flags.ts
|
|
2163
|
+
// src/flags.ts
|
|
2016
2164
|
import { parseArgs } from "node:util";
|
|
2017
2165
|
var flag = source_default.green;
|
|
2018
2166
|
var value = source_default.cyan;
|
|
@@ -2273,7 +2421,7 @@ function resolveTimezone(input) {
|
|
|
2273
2421
|
return tz;
|
|
2274
2422
|
}
|
|
2275
2423
|
|
|
2276
|
-
// tui/state/options.ts
|
|
2424
|
+
// src/tui/state/options.ts
|
|
2277
2425
|
var FIELDS = [
|
|
2278
2426
|
{
|
|
2279
2427
|
key: "since",
|
|
@@ -2445,14 +2593,14 @@ function targetLabelOf(target) {
|
|
|
2445
2593
|
return target + (/^[\d.]+$/.test(target) ? "h" : "");
|
|
2446
2594
|
}
|
|
2447
2595
|
|
|
2448
|
-
// tui/components/ModalFrame.tsx
|
|
2449
|
-
import { jsx as
|
|
2596
|
+
// src/tui/components/ModalFrame.tsx
|
|
2597
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
|
|
2450
2598
|
var MODAL_WIDTH = 64;
|
|
2451
2599
|
function ModalFrame({ title, children }) {
|
|
2452
2600
|
const tabWidth = title.length + 4;
|
|
2453
2601
|
const tabTopRow = `\u250C${"\u2500".repeat(tabWidth - 2)}\u2510`;
|
|
2454
2602
|
const tabTitleSuffix = ` \u2514${"\u2500".repeat(MODAL_WIDTH - tabWidth - 1)}\u2510`;
|
|
2455
|
-
return /* @__PURE__ */
|
|
2603
|
+
return /* @__PURE__ */ jsx10(
|
|
2456
2604
|
"box",
|
|
2457
2605
|
{
|
|
2458
2606
|
position: "absolute",
|
|
@@ -2463,14 +2611,14 @@ function ModalFrame({ title, children }) {
|
|
|
2463
2611
|
alignItems: "center",
|
|
2464
2612
|
justifyContent: "center",
|
|
2465
2613
|
zIndex: 10,
|
|
2466
|
-
children: /* @__PURE__ */
|
|
2467
|
-
/* @__PURE__ */
|
|
2468
|
-
/* @__PURE__ */
|
|
2469
|
-
/* @__PURE__ */
|
|
2470
|
-
/* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */
|
|
2614
|
+
children: /* @__PURE__ */ jsxs9("box", { flexDirection: "column", width: MODAL_WIDTH, children: [
|
|
2615
|
+
/* @__PURE__ */ jsx10("text", { alignSelf: "flex-start", wrapMode: "none", fg: theme.border, bg: theme.bg, children: tabTopRow }),
|
|
2616
|
+
/* @__PURE__ */ jsxs9("text", { wrapMode: "none", bg: theme.bg, children: [
|
|
2617
|
+
/* @__PURE__ */ jsx10("span", { fg: theme.border, children: "\u2502 " }),
|
|
2618
|
+
/* @__PURE__ */ jsx10("span", { fg: theme.muted, children: title }),
|
|
2619
|
+
/* @__PURE__ */ jsx10("span", { fg: theme.border, children: tabTitleSuffix })
|
|
2472
2620
|
] }),
|
|
2473
|
-
/* @__PURE__ */
|
|
2621
|
+
/* @__PURE__ */ jsx10(
|
|
2474
2622
|
"box",
|
|
2475
2623
|
{
|
|
2476
2624
|
border: ["left", "right", "bottom"],
|
|
@@ -2487,7 +2635,7 @@ function ModalFrame({ title, children }) {
|
|
|
2487
2635
|
);
|
|
2488
2636
|
}
|
|
2489
2637
|
function ModalRow({ label, isSelected, children }) {
|
|
2490
|
-
return /* @__PURE__ */
|
|
2638
|
+
return /* @__PURE__ */ jsxs9(
|
|
2491
2639
|
"box",
|
|
2492
2640
|
{
|
|
2493
2641
|
flexDirection: "row",
|
|
@@ -2496,17 +2644,17 @@ function ModalRow({ label, isSelected, children }) {
|
|
|
2496
2644
|
paddingRight: 2,
|
|
2497
2645
|
backgroundColor: isSelected ? theme.selectedBg : void 0,
|
|
2498
2646
|
children: [
|
|
2499
|
-
isSelected ? /* @__PURE__ */
|
|
2500
|
-
/* @__PURE__ */
|
|
2501
|
-
/* @__PURE__ */
|
|
2647
|
+
isSelected ? /* @__PURE__ */ jsx10("text", { position: "absolute", left: -1, wrapMode: "none", fg: theme.accent, bg: theme.bg, children: "\u2503" }) : null,
|
|
2648
|
+
/* @__PURE__ */ jsx10("text", { wrapMode: "none", fg: theme.text, children: label }),
|
|
2649
|
+
/* @__PURE__ */ jsx10("box", { flexGrow: 1 }),
|
|
2502
2650
|
children
|
|
2503
2651
|
]
|
|
2504
2652
|
}
|
|
2505
2653
|
);
|
|
2506
2654
|
}
|
|
2507
2655
|
|
|
2508
|
-
// tui/components/OptionsModal.tsx
|
|
2509
|
-
import { jsx as
|
|
2656
|
+
// src/tui/components/OptionsModal.tsx
|
|
2657
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
|
|
2510
2658
|
var EMPTY_PLACEHOLDERS = {
|
|
2511
2659
|
repos: "(all accessible)",
|
|
2512
2660
|
user: "(authenticated user)",
|
|
@@ -2528,12 +2676,12 @@ function OptionsModal({
|
|
|
2528
2676
|
onSubmit
|
|
2529
2677
|
}) {
|
|
2530
2678
|
const savedState = savedLine(options, saved2);
|
|
2531
|
-
return /* @__PURE__ */
|
|
2532
|
-
SECTIONS.map((section) => /* @__PURE__ */
|
|
2533
|
-
/* @__PURE__ */
|
|
2679
|
+
return /* @__PURE__ */ jsxs10(ModalFrame, { title: "Options", children: [
|
|
2680
|
+
SECTIONS.map((section) => /* @__PURE__ */ jsxs10("box", { flexDirection: "column", marginBottom: 1, children: [
|
|
2681
|
+
/* @__PURE__ */ jsx11("text", { wrapMode: "none", fg: theme.accent, marginLeft: 2, children: section.title }),
|
|
2534
2682
|
section.fields.map((field) => {
|
|
2535
2683
|
const index = FIELDS.indexOf(field);
|
|
2536
|
-
return /* @__PURE__ */
|
|
2684
|
+
return /* @__PURE__ */ jsx11(
|
|
2537
2685
|
FieldRow,
|
|
2538
2686
|
{
|
|
2539
2687
|
field,
|
|
@@ -2547,8 +2695,8 @@ function OptionsModal({
|
|
|
2547
2695
|
);
|
|
2548
2696
|
})
|
|
2549
2697
|
] }, section.title)),
|
|
2550
|
-
/* @__PURE__ */
|
|
2551
|
-
/* @__PURE__ */
|
|
2698
|
+
/* @__PURE__ */ jsx11("text", { wrapMode: "none", fg: savedState.color, marginLeft: 2, marginRight: 2, marginBottom: 1, children: savedState.text }),
|
|
2699
|
+
/* @__PURE__ */ jsx11(
|
|
2552
2700
|
"text",
|
|
2553
2701
|
{
|
|
2554
2702
|
wrapMode: "word",
|
|
@@ -2589,7 +2737,7 @@ function FieldRow({
|
|
|
2589
2737
|
}) {
|
|
2590
2738
|
const value2 = displayValue(field.key, options[field.key]);
|
|
2591
2739
|
const valueColor = value2.isPlaceholder ? isSelected ? theme.muted : theme.dim : theme.muted;
|
|
2592
|
-
return /* @__PURE__ */
|
|
2740
|
+
return /* @__PURE__ */ jsx11(ModalRow, { label: field.label, isSelected, children: isEditing ? /* @__PURE__ */ jsx11(
|
|
2593
2741
|
"input",
|
|
2594
2742
|
{
|
|
2595
2743
|
width: 32,
|
|
@@ -2606,17 +2754,17 @@ function FieldRow({
|
|
|
2606
2754
|
textColor: theme.text,
|
|
2607
2755
|
cursorColor: theme.accent
|
|
2608
2756
|
}
|
|
2609
|
-
) : isSelected && field.kind === "toggle" ? /* @__PURE__ */
|
|
2610
|
-
/* @__PURE__ */
|
|
2611
|
-
/* @__PURE__ */
|
|
2612
|
-
/* @__PURE__ */
|
|
2613
|
-
] }) : isSelected ? /* @__PURE__ */
|
|
2757
|
+
) : isSelected && field.kind === "toggle" ? /* @__PURE__ */ jsxs10("text", { wrapMode: "none", children: [
|
|
2758
|
+
/* @__PURE__ */ jsx11("span", { fg: theme.muted, children: "\u2039 " }),
|
|
2759
|
+
/* @__PURE__ */ jsx11("b", { fg: theme.text, children: value2.text }),
|
|
2760
|
+
/* @__PURE__ */ jsx11("span", { fg: theme.muted, children: " \u203A" })
|
|
2761
|
+
] }) : isSelected ? /* @__PURE__ */ jsx11("text", { wrapMode: "none", children: /* @__PURE__ */ jsx11("b", { fg: value2.isPlaceholder ? theme.muted : theme.text, children: value2.text }) }) : /* @__PURE__ */ jsx11("text", { wrapMode: "none", fg: valueColor, children: value2.text }) });
|
|
2614
2762
|
}
|
|
2615
2763
|
|
|
2616
|
-
// tui/components/SettingsModal.tsx
|
|
2764
|
+
// src/tui/components/SettingsModal.tsx
|
|
2617
2765
|
import { homedir as homedir2 } from "node:os";
|
|
2618
2766
|
|
|
2619
|
-
// tui/state/settings.ts
|
|
2767
|
+
// src/tui/state/settings.ts
|
|
2620
2768
|
var SETTINGS = [
|
|
2621
2769
|
{
|
|
2622
2770
|
key: "noCache",
|
|
@@ -2684,8 +2832,8 @@ var CACHE_MESSAGES = {
|
|
|
2684
2832
|
resetDisabled: { text: "the cache is disabled for this session \xB7 nothing to reset" }
|
|
2685
2833
|
};
|
|
2686
2834
|
|
|
2687
|
-
// tui/components/SettingsModal.tsx
|
|
2688
|
-
import { jsx as
|
|
2835
|
+
// src/tui/components/SettingsModal.tsx
|
|
2836
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
|
|
2689
2837
|
var SECTIONS2 = [];
|
|
2690
2838
|
for (const setting of SETTINGS) {
|
|
2691
2839
|
const last = SECTIONS2.at(-1);
|
|
@@ -2703,12 +2851,12 @@ function SettingsModal({
|
|
|
2703
2851
|
preset
|
|
2704
2852
|
}) {
|
|
2705
2853
|
const message = cacheAction === null ? null : CACHE_MESSAGES[cacheAction];
|
|
2706
|
-
return /* @__PURE__ */
|
|
2707
|
-
SECTIONS2.map((section) => /* @__PURE__ */
|
|
2708
|
-
/* @__PURE__ */
|
|
2854
|
+
return /* @__PURE__ */ jsxs11(ModalFrame, { title: "Settings", children: [
|
|
2855
|
+
SECTIONS2.map((section) => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", marginBottom: 1, children: [
|
|
2856
|
+
/* @__PURE__ */ jsx12("text", { wrapMode: "none", fg: theme.accent, marginLeft: 2, children: section.title }),
|
|
2709
2857
|
section.settings.map((setting) => {
|
|
2710
2858
|
const isSelected = SETTINGS.indexOf(setting) === selected;
|
|
2711
|
-
return /* @__PURE__ */
|
|
2859
|
+
return /* @__PURE__ */ jsx12(ModalRow, { label: setting.label, isSelected, children: /* @__PURE__ */ jsx12(
|
|
2712
2860
|
SettingValue,
|
|
2713
2861
|
{
|
|
2714
2862
|
setting,
|
|
@@ -2721,7 +2869,7 @@ function SettingsModal({
|
|
|
2721
2869
|
) }, setting.key);
|
|
2722
2870
|
})
|
|
2723
2871
|
] }, section.title)),
|
|
2724
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsx12("text", { wrapMode: "word", height: 2, fg: message?.warn ? theme.warn : theme.muted, marginLeft: 2, marginRight: 2, children: message?.text ?? SETTINGS[selected].hint })
|
|
2725
2873
|
] });
|
|
2726
2874
|
}
|
|
2727
2875
|
function SettingValue({
|
|
@@ -2734,22 +2882,22 @@ function SettingValue({
|
|
|
2734
2882
|
}) {
|
|
2735
2883
|
switch (setting.key) {
|
|
2736
2884
|
case "noCache": {
|
|
2737
|
-
return /* @__PURE__ */
|
|
2885
|
+
return /* @__PURE__ */ jsx12(ToggleValue, { value: noCache2 ? "yes" : "no", isSelected });
|
|
2738
2886
|
}
|
|
2739
2887
|
case "clearCache": {
|
|
2740
|
-
return /* @__PURE__ */
|
|
2888
|
+
return /* @__PURE__ */ jsx12(PathValue, { path: cacheDir(), confirming: cacheAction === "confirm", isSelected });
|
|
2741
2889
|
}
|
|
2742
2890
|
case "copyLinks": {
|
|
2743
|
-
return /* @__PURE__ */
|
|
2891
|
+
return /* @__PURE__ */ jsx12(ToggleValue, { value: copyLinks2 ? "yes" : "no", isSelected });
|
|
2744
2892
|
}
|
|
2745
2893
|
case "themePreset": {
|
|
2746
|
-
return /* @__PURE__ */
|
|
2894
|
+
return /* @__PURE__ */ jsx12(ToggleValue, { value: preset, isSelected });
|
|
2747
2895
|
}
|
|
2748
2896
|
case "themeColors": {
|
|
2749
|
-
return /* @__PURE__ */
|
|
2897
|
+
return /* @__PURE__ */ jsx12("text", { wrapMode: "none", children: ["chartDim", "chartBar", "chartLine", "accent"].map((key) => /* @__PURE__ */ jsx12("span", { fg: theme[key], children: "\u2588\u2588" }, key)) });
|
|
2750
2898
|
}
|
|
2751
2899
|
case "resetSettings": {
|
|
2752
|
-
return /* @__PURE__ */
|
|
2900
|
+
return /* @__PURE__ */ jsx12(PathValue, { path: settingsFile(), confirming: cacheAction === "resetConfirm", isSelected });
|
|
2753
2901
|
}
|
|
2754
2902
|
default: {
|
|
2755
2903
|
return null;
|
|
@@ -2758,113 +2906,23 @@ function SettingValue({
|
|
|
2758
2906
|
}
|
|
2759
2907
|
function ToggleValue({ value: value2, isSelected }) {
|
|
2760
2908
|
if (isSelected) {
|
|
2761
|
-
return /* @__PURE__ */
|
|
2762
|
-
/* @__PURE__ */
|
|
2763
|
-
/* @__PURE__ */
|
|
2764
|
-
/* @__PURE__ */
|
|
2909
|
+
return /* @__PURE__ */ jsxs11("text", { wrapMode: "none", children: [
|
|
2910
|
+
/* @__PURE__ */ jsx12("span", { fg: theme.muted, children: "\u2039 " }),
|
|
2911
|
+
/* @__PURE__ */ jsx12("b", { fg: theme.text, children: value2 }),
|
|
2912
|
+
/* @__PURE__ */ jsx12("span", { fg: theme.muted, children: " \u203A" })
|
|
2765
2913
|
] });
|
|
2766
2914
|
}
|
|
2767
|
-
return /* @__PURE__ */
|
|
2915
|
+
return /* @__PURE__ */ jsx12("text", { wrapMode: "none", fg: theme.muted, children: value2 });
|
|
2768
2916
|
}
|
|
2769
2917
|
function PathValue({ path, confirming, isSelected }) {
|
|
2770
2918
|
if (confirming) {
|
|
2771
|
-
return /* @__PURE__ */
|
|
2772
|
-
}
|
|
2773
|
-
return /* @__PURE__ */ jsx11("text", { wrapMode: "none", fg: isSelected ? theme.text : theme.muted, children: path.replace(homedir2(), "~") });
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
// tui/state/browse.ts
|
|
2777
|
-
var TABS = ["1 Awaiting you", "2 Your open PRs", "3 Review time", "4 PR size", "5 Comments"];
|
|
2778
|
-
var initialBrowseState = {
|
|
2779
|
-
tab: 0,
|
|
2780
|
-
scopes: {
|
|
2781
|
-
pending: { view: "list" },
|
|
2782
|
-
open: { view: "list" },
|
|
2783
|
-
review: { view: "list" },
|
|
2784
|
-
size: { view: "list" },
|
|
2785
|
-
comment: { view: "list" }
|
|
2786
|
-
},
|
|
2787
|
-
repoCursors: { pending: 0, open: 0, review: 0, size: 0, comment: 0 },
|
|
2788
|
-
rowCursors: { pending: 0, open: 0 },
|
|
2789
|
-
grouped: { pending: false, open: false }
|
|
2790
|
-
};
|
|
2791
|
-
function dropVanishedRepo(scope, repos) {
|
|
2792
|
-
if (scope.view === "detail" && scope.repo !== null && !repos.some((option) => option.repo === scope.repo)) {
|
|
2793
|
-
return { view: "list" };
|
|
2794
|
-
}
|
|
2795
|
-
return scope;
|
|
2796
|
-
}
|
|
2797
|
-
function cycled(previous, delta, count2) {
|
|
2798
|
-
return (Math.min(previous, count2 - 1) + count2 + delta) % count2;
|
|
2799
|
-
}
|
|
2800
|
-
function browseReducer(state, action) {
|
|
2801
|
-
switch (action.type) {
|
|
2802
|
-
case "tabSelected": {
|
|
2803
|
-
return { ...state, tab: action.tab };
|
|
2804
|
-
}
|
|
2805
|
-
case "tabCycled": {
|
|
2806
|
-
return { ...state, tab: (state.tab + TABS.length + action.delta) % TABS.length };
|
|
2807
|
-
}
|
|
2808
|
-
case "repoCursorMoved": {
|
|
2809
|
-
return {
|
|
2810
|
-
...state,
|
|
2811
|
-
repoCursors: {
|
|
2812
|
-
...state.repoCursors,
|
|
2813
|
-
[action.tab]: cycled(state.repoCursors[action.tab], action.delta, action.count)
|
|
2814
|
-
}
|
|
2815
|
-
};
|
|
2816
|
-
}
|
|
2817
|
-
case "rowCursorMoved": {
|
|
2818
|
-
return {
|
|
2819
|
-
...state,
|
|
2820
|
-
rowCursors: {
|
|
2821
|
-
...state.rowCursors,
|
|
2822
|
-
[action.tab]: cycled(state.rowCursors[action.tab], action.delta, action.count)
|
|
2823
|
-
}
|
|
2824
|
-
};
|
|
2825
|
-
}
|
|
2826
|
-
case "repoOpened": {
|
|
2827
|
-
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "detail", repo: action.repo } } };
|
|
2828
|
-
}
|
|
2829
|
-
case "pickerReturned": {
|
|
2830
|
-
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "list" } } };
|
|
2831
|
-
}
|
|
2832
|
-
case "groupingToggled": {
|
|
2833
|
-
return { ...state, grouped: { ...state.grouped, [action.tab]: !state.grouped[action.tab] } };
|
|
2834
|
-
}
|
|
2835
|
-
case "dataLoaded": {
|
|
2836
|
-
return {
|
|
2837
|
-
...state,
|
|
2838
|
-
scopes: {
|
|
2839
|
-
pending: dropVanishedRepo(state.scopes.pending, action.repos.pending),
|
|
2840
|
-
open: dropVanishedRepo(state.scopes.open, action.repos.open),
|
|
2841
|
-
review: dropVanishedRepo(state.scopes.review, action.repos.review),
|
|
2842
|
-
size: dropVanishedRepo(state.scopes.size, action.repos.size),
|
|
2843
|
-
comment: dropVanishedRepo(state.scopes.comment, action.repos.comment)
|
|
2844
|
-
}
|
|
2845
|
-
};
|
|
2846
|
-
}
|
|
2919
|
+
return /* @__PURE__ */ jsx12("text", { wrapMode: "none", children: /* @__PURE__ */ jsx12("b", { fg: theme.warn, children: "enter to confirm" }) });
|
|
2847
2920
|
}
|
|
2848
|
-
return
|
|
2921
|
+
return /* @__PURE__ */ jsx12("text", { wrapMode: "none", fg: isSelected ? theme.text : theme.muted, children: path.replace(homedir2(), "~") });
|
|
2849
2922
|
}
|
|
2850
2923
|
|
|
2851
|
-
// tui/components/
|
|
2852
|
-
import { jsx as
|
|
2853
|
-
function TabBar({ tab }) {
|
|
2854
|
-
return /* @__PURE__ */ jsx12("box", { flexDirection: "row", height: 1, paddingLeft: 1, marginTop: 1, columnGap: 1, children: TABS.map((label, i) => /* @__PURE__ */ jsx12(
|
|
2855
|
-
"text",
|
|
2856
|
-
{
|
|
2857
|
-
wrapMode: "none",
|
|
2858
|
-
fg: i === tab ? theme.accent : theme.muted,
|
|
2859
|
-
bg: i === tab ? theme.selectedBg : void 0,
|
|
2860
|
-
children: ` ${label} `
|
|
2861
|
-
},
|
|
2862
|
-
label
|
|
2863
|
-
)) });
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
// tui/components/ThemeModal.tsx
|
|
2867
|
-
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
|
|
2924
|
+
// src/tui/components/ThemeModal.tsx
|
|
2925
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
|
|
2868
2926
|
function ThemeModal({
|
|
2869
2927
|
selected,
|
|
2870
2928
|
editing,
|
|
@@ -2877,7 +2935,7 @@ function ThemeModal({
|
|
|
2877
2935
|
const spec = THEME_COLORS[selected];
|
|
2878
2936
|
const message = cacheAction === null ? null : CACHE_MESSAGES[cacheAction];
|
|
2879
2937
|
const hint = spec.key in overrides ? `${spec.hint} \xB7 custom color, an empty value restores the theme` : spec.hint;
|
|
2880
|
-
return /* @__PURE__ */
|
|
2938
|
+
return /* @__PURE__ */ jsxs12(ModalFrame, { title: "Theme colors", children: [
|
|
2881
2939
|
/* @__PURE__ */ jsx13("box", { flexDirection: "column", marginBottom: 1, children: THEME_COLORS.map((color, index) => /* @__PURE__ */ jsx13(
|
|
2882
2940
|
ColorRow,
|
|
2883
2941
|
{
|
|
@@ -2920,8 +2978,8 @@ function ColorRow({
|
|
|
2920
2978
|
textColor: theme.text,
|
|
2921
2979
|
cursorColor: theme.accent
|
|
2922
2980
|
}
|
|
2923
|
-
) : /* @__PURE__ */
|
|
2924
|
-
Array.isArray(swatch) ? /* @__PURE__ */
|
|
2981
|
+
) : /* @__PURE__ */ jsxs12("text", { wrapMode: "none", children: [
|
|
2982
|
+
Array.isArray(swatch) ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
2925
2983
|
/* @__PURE__ */ jsx13("span", { fg: swatch[0], children: "\u2588" }),
|
|
2926
2984
|
/* @__PURE__ */ jsx13("span", { fg: swatch[1], children: "\u2588" }),
|
|
2927
2985
|
/* @__PURE__ */ jsx13("span", { fg: swatch[2], children: "\u2588" }),
|
|
@@ -2932,7 +2990,7 @@ function ColorRow({
|
|
|
2932
2990
|
] }) });
|
|
2933
2991
|
}
|
|
2934
2992
|
|
|
2935
|
-
// tui/hooks/useDeferredLoading.ts
|
|
2993
|
+
// src/tui/hooks/useDeferredLoading.ts
|
|
2936
2994
|
import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
|
|
2937
2995
|
function useDeferredLoading(isLoading, { showDelay = 300, minDuration = 500 } = {}) {
|
|
2938
2996
|
const [visible, setVisible] = useState2(isLoading && showDelay === 0);
|
|
@@ -2960,10 +3018,10 @@ function useDeferredLoading(isLoading, { showDelay = 300, minDuration = 500 } =
|
|
|
2960
3018
|
return visible;
|
|
2961
3019
|
}
|
|
2962
3020
|
|
|
2963
|
-
// tui/hooks/useLoader.ts
|
|
3021
|
+
// src/tui/hooks/useLoader.ts
|
|
2964
3022
|
import { useEffect as useEffect5, useRef as useRef4, useState as useState3 } from "react";
|
|
2965
3023
|
|
|
2966
|
-
// github.ts
|
|
3024
|
+
// src/github.ts
|
|
2967
3025
|
import { execFile } from "node:child_process";
|
|
2968
3026
|
import { createHash } from "node:crypto";
|
|
2969
3027
|
import { statSync } from "node:fs";
|
|
@@ -3207,6 +3265,8 @@ async function fetchPrSizes(prs) {
|
|
|
3207
3265
|
additions
|
|
3208
3266
|
deletions
|
|
3209
3267
|
changedFiles
|
|
3268
|
+
mergedAt
|
|
3269
|
+
closedAt
|
|
3210
3270
|
comments {
|
|
3211
3271
|
totalCount
|
|
3212
3272
|
}
|
|
@@ -3225,7 +3285,7 @@ async function fetchPrSizes(prs) {
|
|
|
3225
3285
|
return prs.map((pr, i) => data[`pr${i}`]?.pullRequest ?? null);
|
|
3226
3286
|
}
|
|
3227
3287
|
|
|
3228
|
-
// data.ts
|
|
3288
|
+
// src/data.ts
|
|
3229
3289
|
var BATCH_SIZE = 25;
|
|
3230
3290
|
var MAX_CONCURRENT_BATCHES = 4;
|
|
3231
3291
|
function createLimiter(maxConcurrent) {
|
|
@@ -3407,6 +3467,8 @@ async function fetchSizeRaw(prs, onProgress, options = {}) {
|
|
|
3407
3467
|
additions: details.additions,
|
|
3408
3468
|
deletions: details.deletions,
|
|
3409
3469
|
total: details.additions + details.deletions,
|
|
3470
|
+
mergedAt: details.mergedAt === null ? null : new Date(details.mergedAt),
|
|
3471
|
+
closedAt: details.closedAt === null ? null : new Date(details.closedAt),
|
|
3410
3472
|
comments: { discussion, review, total: discussion + review }
|
|
3411
3473
|
});
|
|
3412
3474
|
}
|
|
@@ -3414,7 +3476,7 @@ async function fetchSizeRaw(prs, onProgress, options = {}) {
|
|
|
3414
3476
|
return { sizes, cacheHits };
|
|
3415
3477
|
}
|
|
3416
3478
|
|
|
3417
|
-
// tui/data/load.ts
|
|
3479
|
+
// src/tui/data/load.ts
|
|
3418
3480
|
function reviveRawData(data) {
|
|
3419
3481
|
return {
|
|
3420
3482
|
...data,
|
|
@@ -3430,7 +3492,12 @@ function reviveRawData(data) {
|
|
|
3430
3492
|
return { ...result, pr };
|
|
3431
3493
|
}),
|
|
3432
3494
|
sizes: data.sizes.map((entry) => {
|
|
3433
|
-
return {
|
|
3495
|
+
return {
|
|
3496
|
+
...entry,
|
|
3497
|
+
pr: { ...entry.pr, createdAt: new Date(entry.pr.createdAt) },
|
|
3498
|
+
mergedAt: entry.mergedAt === null ? null : new Date(entry.mergedAt),
|
|
3499
|
+
closedAt: entry.closedAt === null ? null : new Date(entry.closedAt)
|
|
3500
|
+
};
|
|
3434
3501
|
})
|
|
3435
3502
|
};
|
|
3436
3503
|
}
|
|
@@ -3530,7 +3597,7 @@ async function loadData(options, onPhase, { bypassCache = false } = {}) {
|
|
|
3530
3597
|
return data;
|
|
3531
3598
|
}
|
|
3532
3599
|
|
|
3533
|
-
// tui/hooks/useLoader.ts
|
|
3600
|
+
// src/tui/hooks/useLoader.ts
|
|
3534
3601
|
function useLoader(options, noCache2, onLoaded) {
|
|
3535
3602
|
const [startupSnapshot] = useState3(() => noCache2 ? null : loadSnapshot(options));
|
|
3536
3603
|
const [raw, setRaw] = useState3(startupSnapshot);
|
|
@@ -3601,10 +3668,10 @@ function useLoader(options, noCache2, onLoaded) {
|
|
|
3601
3668
|
};
|
|
3602
3669
|
}
|
|
3603
3670
|
|
|
3604
|
-
// tui/hooks/useViewModel.ts
|
|
3671
|
+
// src/tui/hooks/useViewModel.ts
|
|
3605
3672
|
import { useMemo } from "react";
|
|
3606
3673
|
|
|
3607
|
-
// tui/views/repos.ts
|
|
3674
|
+
// src/tui/views/repos.ts
|
|
3608
3675
|
function reviewDetail(counts) {
|
|
3609
3676
|
return `${counts.reviewed} reviewed` + (counts.pending > 0 ? `, ${counts.pending} pending` : "");
|
|
3610
3677
|
}
|
|
@@ -3698,6 +3765,38 @@ function buildOpenRepoOptions(raw) {
|
|
|
3698
3765
|
})
|
|
3699
3766
|
];
|
|
3700
3767
|
}
|
|
3768
|
+
function mergedDetail(counts) {
|
|
3769
|
+
return `${counts.merged} merged` + (counts.closed > 0 ? `, ${counts.closed} closed unmerged` : "");
|
|
3770
|
+
}
|
|
3771
|
+
function buildMergedRepoOptions(raw) {
|
|
3772
|
+
const countsByRepo = /* @__PURE__ */ new Map();
|
|
3773
|
+
for (const size of raw.sizes) {
|
|
3774
|
+
const counts = countsByRepo.get(size.pr.repo) ?? { merged: 0, closed: 0 };
|
|
3775
|
+
if (size.mergedAt !== null) {
|
|
3776
|
+
counts.merged += 1;
|
|
3777
|
+
} else if (size.pr.state !== "open") {
|
|
3778
|
+
counts.closed += 1;
|
|
3779
|
+
}
|
|
3780
|
+
countsByRepo.set(size.pr.repo, counts);
|
|
3781
|
+
}
|
|
3782
|
+
if (countsByRepo.size < 2) {
|
|
3783
|
+
return [];
|
|
3784
|
+
}
|
|
3785
|
+
const entries = [...countsByRepo.entries()].toSorted(
|
|
3786
|
+
(a, b) => b[1].merged - a[1].merged || b[1].closed - a[1].closed || a[0].localeCompare(b[0])
|
|
3787
|
+
);
|
|
3788
|
+
const totals = { merged: 0, closed: 0 };
|
|
3789
|
+
for (const [, counts] of entries) {
|
|
3790
|
+
totals.merged += counts.merged;
|
|
3791
|
+
totals.closed += counts.closed;
|
|
3792
|
+
}
|
|
3793
|
+
return [
|
|
3794
|
+
{ repo: null, label: "All repos", detail: mergedDetail(totals) },
|
|
3795
|
+
...entries.map(([repo, counts]) => {
|
|
3796
|
+
return { repo, label: repo, detail: mergedDetail(counts) };
|
|
3797
|
+
})
|
|
3798
|
+
];
|
|
3799
|
+
}
|
|
3701
3800
|
function commentDetail(comments, prs) {
|
|
3702
3801
|
return `${comments} ${comments === 1 ? "comment" : "comments"} on ${prs} ${prs === 1 ? "PR" : "PRs"}`;
|
|
3703
3802
|
}
|
|
@@ -3728,7 +3827,7 @@ function buildCommentRepoOptions(raw) {
|
|
|
3728
3827
|
];
|
|
3729
3828
|
}
|
|
3730
3829
|
|
|
3731
|
-
// tui/views/charts/draw.ts
|
|
3830
|
+
// src/tui/views/charts/draw.ts
|
|
3732
3831
|
function blankCells(width) {
|
|
3733
3832
|
return Array.from({ length: width }, () => {
|
|
3734
3833
|
return { ch: " " };
|
|
@@ -3781,7 +3880,7 @@ function hbar(fraction, width, color) {
|
|
|
3781
3880
|
return line;
|
|
3782
3881
|
}
|
|
3783
3882
|
|
|
3784
|
-
// tui/views/charts/distribution.ts
|
|
3883
|
+
// src/tui/views/charts/distribution.ts
|
|
3785
3884
|
var DURATION_TICKS = [5 / 60, 0.25, 0.5, 1, 2, 4, 8, 24, 48, 96, 168, 336, 720, 2160];
|
|
3786
3885
|
var COUNT_TICKS = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4, 25e3, 5e4, 1e5];
|
|
3787
3886
|
function buildDistribution({ values, width, format, ticks, flat }) {
|
|
@@ -3860,7 +3959,7 @@ function buildDistribution({ values, width, format, ticks, flat }) {
|
|
|
3860
3959
|
};
|
|
3861
3960
|
}
|
|
3862
3961
|
|
|
3863
|
-
// tui/views/charts/gauge.ts
|
|
3962
|
+
// src/tui/views/charts/gauge.ts
|
|
3864
3963
|
var GAUGE_BAR_WIDTH = 24;
|
|
3865
3964
|
function buildGaugeCard({ title, subtitle, rows }) {
|
|
3866
3965
|
const total = rows.reduce((sum, row) => sum + row.count, 0);
|
|
@@ -3883,7 +3982,7 @@ function buildGaugeCard({ title, subtitle, rows }) {
|
|
|
3883
3982
|
return { title, subtitle, lines };
|
|
3884
3983
|
}
|
|
3885
3984
|
|
|
3886
|
-
// tui/views/charts/heatmap.ts
|
|
3985
|
+
// src/tui/views/charts/heatmap.ts
|
|
3887
3986
|
var WEEKDAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
3888
3987
|
function heatColor(count2) {
|
|
3889
3988
|
if (count2 === 1) {
|
|
@@ -3949,7 +4048,7 @@ function buildHeatmapCard({ title, subtitle, grid, columns, legend }) {
|
|
|
3949
4048
|
return { title, subtitle, lines };
|
|
3950
4049
|
}
|
|
3951
4050
|
|
|
3952
|
-
// tui/views/charts/histogram.ts
|
|
4051
|
+
// src/tui/views/charts/histogram.ts
|
|
3953
4052
|
var HIST_BAR_WIDTH = 36;
|
|
3954
4053
|
function axisStep(maxCount) {
|
|
3955
4054
|
for (const step of [1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1e3, 2e3, 5e3]) {
|
|
@@ -4001,7 +4100,7 @@ function buildHistogramCard({ title, subtitle, values, buckets, format }) {
|
|
|
4001
4100
|
return { title, subtitle, lines };
|
|
4002
4101
|
}
|
|
4003
4102
|
|
|
4004
|
-
// tui/views/charts/scatter.ts
|
|
4103
|
+
// src/tui/views/charts/scatter.ts
|
|
4005
4104
|
var SCATTER_WIDTH = 36;
|
|
4006
4105
|
var SCATTER_HEIGHT = 8;
|
|
4007
4106
|
var DOT_BITS = [
|
|
@@ -4051,7 +4150,7 @@ function buildScatterCard({ title, subtitle, points, formatX, formatY }) {
|
|
|
4051
4150
|
return { title, subtitle, lines };
|
|
4052
4151
|
}
|
|
4053
4152
|
|
|
4054
|
-
// tui/views/charts/spread.ts
|
|
4153
|
+
// src/tui/views/charts/spread.ts
|
|
4055
4154
|
var SPREAD_STRIP_WIDTH = 32;
|
|
4056
4155
|
function buildSpreadCard(title, metrics, format) {
|
|
4057
4156
|
const rows = metrics.map(({ label, values }) => {
|
|
@@ -4096,10 +4195,10 @@ function buildSpreadCard(title, metrics, format) {
|
|
|
4096
4195
|
return { title, subtitle, lines };
|
|
4097
4196
|
}
|
|
4098
4197
|
|
|
4099
|
-
// tui/views/charts/trend.ts
|
|
4198
|
+
// src/tui/views/charts/trend.ts
|
|
4100
4199
|
var asciichart = __toESM(require_asciichart(), 1);
|
|
4101
4200
|
|
|
4102
|
-
// tui/views/charts/weeks.ts
|
|
4201
|
+
// src/tui/views/charts/weeks.ts
|
|
4103
4202
|
var DAY_MS = 864e5;
|
|
4104
4203
|
var WEEK_MS = 7 * DAY_MS;
|
|
4105
4204
|
function mondayOf(dayUtcMs) {
|
|
@@ -4123,7 +4222,7 @@ function weekAxisRow(width, prefix, points, columnOf, mondayOfPoint) {
|
|
|
4123
4222
|
return mergeCells(cells);
|
|
4124
4223
|
}
|
|
4125
4224
|
|
|
4126
|
-
// tui/views/charts/trend.ts
|
|
4225
|
+
// src/tui/views/charts/trend.ts
|
|
4127
4226
|
var TREND_POINTS = 40;
|
|
4128
4227
|
var TREND_HEIGHT = 6;
|
|
4129
4228
|
function axisSplit(line) {
|
|
@@ -4217,7 +4316,7 @@ function buildTrendCard({ title, entries, format, floor }) {
|
|
|
4217
4316
|
return { title, subtitle, lines };
|
|
4218
4317
|
}
|
|
4219
4318
|
|
|
4220
|
-
// tui/views/charts/volume.ts
|
|
4319
|
+
// src/tui/views/charts/volume.ts
|
|
4221
4320
|
var VOLUME_ROWS = 4;
|
|
4222
4321
|
var VOLUME_MAX_BARS = 18;
|
|
4223
4322
|
var V_PARTIALS = [" ", "\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
@@ -4273,7 +4372,7 @@ function buildVolumeCard(title, dates, weights) {
|
|
|
4273
4372
|
return { title, subtitle, lines };
|
|
4274
4373
|
}
|
|
4275
4374
|
|
|
4276
|
-
// tui/views/stats.ts
|
|
4375
|
+
// src/tui/views/stats.ts
|
|
4277
4376
|
function countCell(count2, label, dimWhenZero = false) {
|
|
4278
4377
|
const dim2 = dimWhenZero && count2 === 0;
|
|
4279
4378
|
return [
|
|
@@ -4578,11 +4677,122 @@ function buildCommentView(raw, repo = null, width = 100) {
|
|
|
4578
4677
|
}
|
|
4579
4678
|
return { empty: null, ...base, strip, headline, left, right, distribution, lists };
|
|
4580
4679
|
}
|
|
4680
|
+
function buildMergedView(raw, repo = null, width = 100) {
|
|
4681
|
+
const base = {
|
|
4682
|
+
strip: [],
|
|
4683
|
+
headline: null,
|
|
4684
|
+
distributionTitle: "Time to merge distribution",
|
|
4685
|
+
noCharts: "No merged PRs to chart.",
|
|
4686
|
+
left: [],
|
|
4687
|
+
right: [],
|
|
4688
|
+
distribution: null,
|
|
4689
|
+
lists: []
|
|
4690
|
+
};
|
|
4691
|
+
if (raw.authoredTotal === 0) {
|
|
4692
|
+
return { empty: "No authored PRs found.", ...base };
|
|
4693
|
+
}
|
|
4694
|
+
const sizes = repo === null ? raw.sizes : raw.sizes.filter((size) => size.pr.repo === repo);
|
|
4695
|
+
if (sizes.length === 0) {
|
|
4696
|
+
return { empty: "No accessible authored PRs to analyze.", ...base };
|
|
4697
|
+
}
|
|
4698
|
+
const stats = computeMergeStats(sizes);
|
|
4699
|
+
const strip = [
|
|
4700
|
+
countCell(sizes.length, "PRs created"),
|
|
4701
|
+
countCell(stats.merged.length, "merged"),
|
|
4702
|
+
countCell(stats.closed.length, "closed unmerged", true),
|
|
4703
|
+
countCell(stats.open.length, "still open", true)
|
|
4704
|
+
];
|
|
4705
|
+
if (repo === null) {
|
|
4706
|
+
strip.push([{ text: `${raw.authoredTotal - raw.sizes.length} inaccessible (excluded)`, fg: theme.dim }]);
|
|
4707
|
+
}
|
|
4708
|
+
const lists = [];
|
|
4709
|
+
if (stats.merged.length > 0) {
|
|
4710
|
+
const top = stats.merged.slice(0, 5);
|
|
4711
|
+
lists.push({
|
|
4712
|
+
title: "Recently merged PRs",
|
|
4713
|
+
rows: toPrRows(
|
|
4714
|
+
top.map((result) => result.entry),
|
|
4715
|
+
top.map((result) => `${durationLead(result)} to merge`)
|
|
4716
|
+
)
|
|
4717
|
+
});
|
|
4718
|
+
}
|
|
4719
|
+
if (stats.closed.length > 0) {
|
|
4720
|
+
const top = stats.closed.slice(0, 5);
|
|
4721
|
+
lists.push({
|
|
4722
|
+
title: "Closed without merging",
|
|
4723
|
+
rows: toPrRows(
|
|
4724
|
+
top.map((result) => result.entry),
|
|
4725
|
+
top.map((result) => `${durationLead(result)} to close`)
|
|
4726
|
+
)
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
if (stats.merged.length === 0) {
|
|
4730
|
+
return { empty: null, ...base, strip, lists };
|
|
4731
|
+
}
|
|
4732
|
+
const sorted = [...stats.allHours].toSorted((a, b) => a - b);
|
|
4733
|
+
const headline = [
|
|
4734
|
+
{ text: "p50 ", fg: theme.muted },
|
|
4735
|
+
{ text: formatDuration(percentile(sorted, 50)), fg: theme.accent },
|
|
4736
|
+
{ text: " p90 ", fg: theme.muted },
|
|
4737
|
+
{ text: formatDuration(percentile(sorted, 90)), fg: theme.accent },
|
|
4738
|
+
{ text: ` ${stats.merged.length} of ${sizes.length} PRs merged`, fg: theme.muted }
|
|
4739
|
+
];
|
|
4740
|
+
const mergeDates = stats.merged.map((result) => result.mergedAt);
|
|
4741
|
+
const created = sizes.map((size) => size.pr.createdAt);
|
|
4742
|
+
const left = [
|
|
4743
|
+
buildHistogramCard({
|
|
4744
|
+
title: "Time to merge",
|
|
4745
|
+
subtitle: "elapsed time, created \u2192 merged",
|
|
4746
|
+
values: stats.allHours,
|
|
4747
|
+
buckets: currentBuckets(),
|
|
4748
|
+
format: formatDuration
|
|
4749
|
+
}),
|
|
4750
|
+
buildHeatmapCard({
|
|
4751
|
+
title: "When your PRs merge",
|
|
4752
|
+
subtitle: "PRs merged, weekday \xD7 hour, local time",
|
|
4753
|
+
grid: mergeDates,
|
|
4754
|
+
columns: [
|
|
4755
|
+
{ label: "merged", dates: mergeDates },
|
|
4756
|
+
{ label: "created", dates: created, muted: true }
|
|
4757
|
+
],
|
|
4758
|
+
legend: "PRs merged in that hour"
|
|
4759
|
+
}),
|
|
4760
|
+
buildGaugeCard({
|
|
4761
|
+
title: "Outcomes",
|
|
4762
|
+
subtitle: "where your authored PRs ended up",
|
|
4763
|
+
rows: [
|
|
4764
|
+
{ label: "merged", count: stats.merged.length, color: theme.accent },
|
|
4765
|
+
{ label: "closed unmerged", count: stats.closed.length, color: theme.warn },
|
|
4766
|
+
...stats.open.length > 0 ? [{ label: "still open", count: stats.open.length, color: theme.chartDim }] : []
|
|
4767
|
+
]
|
|
4768
|
+
})
|
|
4769
|
+
];
|
|
4770
|
+
const right = [
|
|
4771
|
+
buildTrendCard({
|
|
4772
|
+
title: "Time to merge trend",
|
|
4773
|
+
entries: stats.merged.map((result) => {
|
|
4774
|
+
return { date: result.mergedAt, value: result.hours };
|
|
4775
|
+
}),
|
|
4776
|
+
format: formatDuration,
|
|
4777
|
+
floor: 1 / 60
|
|
4778
|
+
}),
|
|
4779
|
+
buildVolumeCard("PRs merged per week", mergeDates),
|
|
4780
|
+
buildVolumeCard("PRs created per week", created)
|
|
4781
|
+
];
|
|
4782
|
+
const distribution = buildDistribution({
|
|
4783
|
+
values: stats.allHours,
|
|
4784
|
+
width: Math.max(width - 3, 40),
|
|
4785
|
+
format: formatDuration,
|
|
4786
|
+
ticks: DURATION_TICKS,
|
|
4787
|
+
flat: (n, value2) => `all ${n} merged ${n === 1 ? "PR" : "PRs"} took ${value2}`
|
|
4788
|
+
});
|
|
4789
|
+
return { empty: null, ...base, strip, headline, left, right, distribution, lists };
|
|
4790
|
+
}
|
|
4581
4791
|
function count(value2) {
|
|
4582
4792
|
return formatCount(Math.round(value2));
|
|
4583
4793
|
}
|
|
4584
4794
|
|
|
4585
|
-
// tui/hooks/useViewModel.ts
|
|
4795
|
+
// src/tui/hooks/useViewModel.ts
|
|
4586
4796
|
function resolveScope(scope, repos) {
|
|
4587
4797
|
if (repos.length === 0) {
|
|
4588
4798
|
return { view: "detail", repo: null };
|
|
@@ -4605,11 +4815,13 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
|
|
|
4605
4815
|
const sizeTarget = options.sizeTarget === "" ? void 0 : parseSizeTarget(options.sizeTarget);
|
|
4606
4816
|
const pendingRepos = buildPendingRepoOptions(raw);
|
|
4607
4817
|
const openRepos = buildOpenRepoOptions(raw);
|
|
4818
|
+
const mergedRepos = buildMergedRepoOptions(raw);
|
|
4608
4819
|
const reviewRepos = buildReviewRepoOptions(raw);
|
|
4609
4820
|
const sizeRepos = buildSizeRepoOptions(raw);
|
|
4610
4821
|
const commentRepos = buildCommentRepoOptions(raw);
|
|
4611
4822
|
const pendingScope = resolveScope(scopes.pending, pendingRepos);
|
|
4612
4823
|
const openScope = resolveScope(scopes.open, openRepos);
|
|
4824
|
+
const mergedScope = resolveScope(scopes.merged, mergedRepos);
|
|
4613
4825
|
const reviewScope = resolveScope(scopes.review, reviewRepos);
|
|
4614
4826
|
const sizeScope = resolveScope(scopes.size, sizeRepos);
|
|
4615
4827
|
const commentScope = resolveScope(scopes.comment, commentRepos);
|
|
@@ -4617,16 +4829,19 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
|
|
|
4617
4829
|
return {
|
|
4618
4830
|
pendingRepos,
|
|
4619
4831
|
openRepos,
|
|
4832
|
+
mergedRepos,
|
|
4620
4833
|
reviewRepos,
|
|
4621
4834
|
sizeRepos,
|
|
4622
4835
|
commentRepos,
|
|
4623
4836
|
pendingScope,
|
|
4624
4837
|
openScope,
|
|
4838
|
+
mergedScope,
|
|
4625
4839
|
reviewScope,
|
|
4626
4840
|
sizeScope,
|
|
4627
4841
|
commentScope,
|
|
4628
4842
|
pending: pendingScope.view === "detail" ? buildPendingReviewView(raw, pendingScope.repo, grouping.pending) : null,
|
|
4629
4843
|
open: openScope.view === "detail" ? buildOpenAuthoredView(raw, openScope.repo, grouping.open) : null,
|
|
4844
|
+
merged: mergedScope.view === "detail" ? buildMergedView(raw, mergedScope.repo, width) : null,
|
|
4630
4845
|
review,
|
|
4631
4846
|
size: sizeScope.view === "detail" ? buildSizeView(raw, sizeTarget, sizeScope.repo, width) : null,
|
|
4632
4847
|
comments: commentScope.view === "detail" ? buildCommentView(raw, commentScope.repo, width) : null
|
|
@@ -4641,6 +4856,7 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
|
|
|
4641
4856
|
width,
|
|
4642
4857
|
scopes.pending,
|
|
4643
4858
|
scopes.open,
|
|
4859
|
+
scopes.merged,
|
|
4644
4860
|
scopes.review,
|
|
4645
4861
|
scopes.size,
|
|
4646
4862
|
scopes.comment,
|
|
@@ -4650,7 +4866,7 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
|
|
|
4650
4866
|
]);
|
|
4651
4867
|
}
|
|
4652
4868
|
|
|
4653
|
-
// tui/keymap.ts
|
|
4869
|
+
// src/tui/keymap.ts
|
|
4654
4870
|
function handleOptionsModalKey(key, context) {
|
|
4655
4871
|
switch (key.name) {
|
|
4656
4872
|
case "escape": {
|
|
@@ -4871,6 +5087,13 @@ function handleQueueKey(key, context) {
|
|
|
4871
5087
|
}
|
|
4872
5088
|
function statsTabOf(context) {
|
|
4873
5089
|
const { views } = context;
|
|
5090
|
+
if (context.browse.tab === 1) {
|
|
5091
|
+
return {
|
|
5092
|
+
key: "merged",
|
|
5093
|
+
repos: views?.mergedRepos ?? [],
|
|
5094
|
+
scope: views?.mergedScope ?? null
|
|
5095
|
+
};
|
|
5096
|
+
}
|
|
4874
5097
|
if (context.browse.tab === 2) {
|
|
4875
5098
|
return {
|
|
4876
5099
|
key: "review",
|
|
@@ -4957,14 +5180,16 @@ function handleAppKey(key, context) {
|
|
|
4957
5180
|
context.dispatchBrowse({ type: "tabCycled", delta: -1 });
|
|
4958
5181
|
} else if (key.name === "right" || key.name === "tab") {
|
|
4959
5182
|
context.dispatchBrowse({ type: "tabCycled", delta: 1 });
|
|
4960
|
-
} else if (context.browse.tab
|
|
5183
|
+
} else if (context.browse.tab === 1 && key.name === "t") {
|
|
5184
|
+
context.dispatchBrowse({ type: "subTabToggled" });
|
|
5185
|
+
} else if (context.browse.tab === 0 || context.browse.tab === 1 && context.browse.authoredTab === "open") {
|
|
4961
5186
|
handleQueueKey(key, context);
|
|
4962
5187
|
} else {
|
|
4963
5188
|
handleStatsKey(key, context);
|
|
4964
5189
|
}
|
|
4965
5190
|
}
|
|
4966
5191
|
|
|
4967
|
-
// tui/state/ui.ts
|
|
5192
|
+
// src/tui/state/ui.ts
|
|
4968
5193
|
var initialUiState = {
|
|
4969
5194
|
modal: null,
|
|
4970
5195
|
editing: false,
|
|
@@ -5061,7 +5286,7 @@ function uiReducer(state, action) {
|
|
|
5061
5286
|
return state;
|
|
5062
5287
|
}
|
|
5063
5288
|
|
|
5064
|
-
// tui/utils/browser.ts
|
|
5289
|
+
// src/tui/utils/browser.ts
|
|
5065
5290
|
import { spawn } from "node:child_process";
|
|
5066
5291
|
function openCommand(url) {
|
|
5067
5292
|
switch (process.platform) {
|
|
@@ -5090,7 +5315,7 @@ function openInBrowser(url, onError) {
|
|
|
5090
5315
|
child.unref();
|
|
5091
5316
|
}
|
|
5092
5317
|
|
|
5093
|
-
// tui/utils/clipboard.ts
|
|
5318
|
+
// src/tui/utils/clipboard.ts
|
|
5094
5319
|
import { spawn as spawn2 } from "node:child_process";
|
|
5095
5320
|
function copyCommand() {
|
|
5096
5321
|
switch (process.platform) {
|
|
@@ -5124,8 +5349,8 @@ function copyToClipboard(text, onError) {
|
|
|
5124
5349
|
child.stdin.end(text);
|
|
5125
5350
|
}
|
|
5126
5351
|
|
|
5127
|
-
// tui/App.tsx
|
|
5128
|
-
import { jsx as jsx14, jsxs as
|
|
5352
|
+
// src/tui/App.tsx
|
|
5353
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "@opentui/react/jsx-runtime";
|
|
5129
5354
|
function App({
|
|
5130
5355
|
initial: initial2,
|
|
5131
5356
|
initialSaved = null,
|
|
@@ -5164,6 +5389,7 @@ function App({
|
|
|
5164
5389
|
const reviewScrollRef = useRef5(null);
|
|
5165
5390
|
const sizeScrollRef = useRef5(null);
|
|
5166
5391
|
const commentScrollRef = useRef5(null);
|
|
5392
|
+
const mergedScrollRef = useRef5(null);
|
|
5167
5393
|
const { raw, isSnapshot, loading, load, error, stale, reload } = useLoader(options, noCache2, (data) => {
|
|
5168
5394
|
dispatchBrowse({
|
|
5169
5395
|
type: "dataLoaded",
|
|
@@ -5172,7 +5398,8 @@ function App({
|
|
|
5172
5398
|
open: buildOpenRepoOptions(data),
|
|
5173
5399
|
review: buildReviewRepoOptions(data),
|
|
5174
5400
|
size: buildSizeRepoOptions(data),
|
|
5175
|
-
comment: buildCommentRepoOptions(data)
|
|
5401
|
+
comment: buildCommentRepoOptions(data),
|
|
5402
|
+
merged: buildMergedRepoOptions(data)
|
|
5176
5403
|
}
|
|
5177
5404
|
});
|
|
5178
5405
|
});
|
|
@@ -5235,13 +5462,13 @@ function App({
|
|
|
5235
5462
|
dispatchUi({ type: "editStarted" });
|
|
5236
5463
|
},
|
|
5237
5464
|
scrollBy: (forTab, delta) => {
|
|
5238
|
-
const ref = forTab === 2 ? reviewScrollRef : forTab === 3 ? sizeScrollRef : commentScrollRef;
|
|
5465
|
+
const ref = forTab === 1 ? mergedScrollRef : forTab === 2 ? reviewScrollRef : forTab === 3 ? sizeScrollRef : commentScrollRef;
|
|
5239
5466
|
ref.current?.scrollBy(delta);
|
|
5240
5467
|
}
|
|
5241
5468
|
});
|
|
5242
5469
|
});
|
|
5243
5470
|
const capWarning = raw?.searchCapped ? "Warning, a search hit the 1000 result cap, so data may be incomplete. Narrow since or repos." : null;
|
|
5244
|
-
return /* @__PURE__ */
|
|
5471
|
+
return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: theme.bg, children: [
|
|
5245
5472
|
/* @__PURE__ */ jsx14(Header, { options, raw, error, spinning: showLoad }),
|
|
5246
5473
|
/* @__PURE__ */ jsx14(TabBar, { tab: browse.tab }),
|
|
5247
5474
|
/* @__PURE__ */ jsx14(
|
|
@@ -5251,7 +5478,12 @@ function App({
|
|
|
5251
5478
|
browse,
|
|
5252
5479
|
warning: capWarning,
|
|
5253
5480
|
focused: ui.modal === null,
|
|
5254
|
-
scrollRefs: {
|
|
5481
|
+
scrollRefs: {
|
|
5482
|
+
review: reviewScrollRef,
|
|
5483
|
+
size: sizeScrollRef,
|
|
5484
|
+
comment: commentScrollRef,
|
|
5485
|
+
merged: mergedScrollRef
|
|
5486
|
+
},
|
|
5255
5487
|
error,
|
|
5256
5488
|
loading,
|
|
5257
5489
|
load,
|
|
@@ -5265,6 +5497,7 @@ function App({
|
|
|
5265
5497
|
modal: ui.modal,
|
|
5266
5498
|
editing: ui.editing,
|
|
5267
5499
|
tab: browse.tab,
|
|
5500
|
+
authoredTab: browse.authoredTab,
|
|
5268
5501
|
views,
|
|
5269
5502
|
copyLinks: copyLinks2,
|
|
5270
5503
|
openError: ui.openError,
|
|
@@ -5313,7 +5546,7 @@ function App({
|
|
|
5313
5546
|
] });
|
|
5314
5547
|
}
|
|
5315
5548
|
|
|
5316
|
-
// tui/bootstrap.ts
|
|
5549
|
+
// src/tui/bootstrap.ts
|
|
5317
5550
|
function bootstrap() {
|
|
5318
5551
|
const { values, explicit } = parseCliArgs();
|
|
5319
5552
|
if (values.help) {
|
|
@@ -5352,7 +5585,7 @@ function bootstrap() {
|
|
|
5352
5585
|
}
|
|
5353
5586
|
}
|
|
5354
5587
|
|
|
5355
|
-
// tui/main.tsx
|
|
5588
|
+
// src/tui/main.tsx
|
|
5356
5589
|
import { jsx as jsx15 } from "@opentui/react/jsx-runtime";
|
|
5357
5590
|
var { initial, saved, noCache, copyLinks, theme: theme2 } = bootstrap();
|
|
5358
5591
|
var renderer = await createCliRenderer({ exitOnCtrlC: true });
|