@kud/gh-ink 0.11.1 → 0.13.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 +16 -1
- package/dist/index.js +222 -25
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,13 @@ type JiraRow = {
|
|
|
85
85
|
age: string;
|
|
86
86
|
indent: boolean;
|
|
87
87
|
instanceKey?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Trailing annotation, rendered dim after the summary — a recurrence marker,
|
|
90
|
+
* a source hint, anything secondary to the title. Its own node rather than
|
|
91
|
+
* part of `summary` so it can be dimmed, and so its width is measured
|
|
92
|
+
* separately instead of being smuggled past the truncation maths.
|
|
93
|
+
*/
|
|
94
|
+
note?: string;
|
|
88
95
|
};
|
|
89
96
|
type RepoHeader = {
|
|
90
97
|
kind: "repo-header";
|
|
@@ -225,7 +232,7 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
|
|
|
225
232
|
actions: Action[];
|
|
226
233
|
cursor: number;
|
|
227
234
|
}) => React.JSX.Element;
|
|
228
|
-
declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, extensions, tabHelp, emptyHint, }: {
|
|
235
|
+
declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, extensions, tabHelp, emptyHint, }: {
|
|
229
236
|
fetcher: () => Promise<{
|
|
230
237
|
sections: Section[];
|
|
231
238
|
login: string;
|
|
@@ -246,6 +253,14 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIn
|
|
|
246
253
|
ciJob?: string;
|
|
247
254
|
ciFetcher?: () => Promise<CiStatus | null>;
|
|
248
255
|
ciPollMs?: number;
|
|
256
|
+
/**
|
|
257
|
+
* A file something else touches when it has changed GitHub on your behalf —
|
|
258
|
+
* a Claude session closing an issue, a script merging a PR. Touching it makes
|
|
259
|
+
* the inbox refetch; it never repaints on its own.
|
|
260
|
+
*/
|
|
261
|
+
watchPath?: string;
|
|
262
|
+
/** Bursts of writes to collapse into one refetch. */
|
|
263
|
+
watchDebounceMs?: number;
|
|
249
264
|
extensions?: InboxExtension[];
|
|
250
265
|
}) => React.JSX.Element;
|
|
251
266
|
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React2, { createContext, useState, useRef, useEffect, useContext } from 'react';
|
|
1
|
+
import React2, { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
2
2
|
import { useWindowSize, useInput, Text, Box } from 'ink';
|
|
3
3
|
import { colors, ScrollView, TextInput, LoadingScreen, Switch, StatusMessage, FooterHints, useListCursor, Tabs } from '@kud/ink-ui';
|
|
4
4
|
import { isPassCheck, isFailCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { $ } from 'zx';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
|
-
import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, rmSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
8
|
+
import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, watch, rmSync } from 'fs';
|
|
9
|
+
import { join, dirname, basename } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
11
|
|
|
12
12
|
// src/components/comments-panel.tsx
|
|
@@ -637,6 +637,102 @@ var invalidateCache = (key) => {
|
|
|
637
637
|
} catch {
|
|
638
638
|
}
|
|
639
639
|
};
|
|
640
|
+
|
|
641
|
+
// src/inbox/diff.ts
|
|
642
|
+
var keyOf = (item) => {
|
|
643
|
+
if (item.kind === "pr" || item.kind === "issue") return item.url;
|
|
644
|
+
if (item.kind === "jira") {
|
|
645
|
+
const row = item;
|
|
646
|
+
return row.url || `jira:${row.key}`;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
};
|
|
650
|
+
var renderedState = (item) => {
|
|
651
|
+
if (item.kind === "pr" || item.kind === "issue") {
|
|
652
|
+
const i = item;
|
|
653
|
+
return [
|
|
654
|
+
i.title,
|
|
655
|
+
i.health,
|
|
656
|
+
i.unresolved,
|
|
657
|
+
i.conversation,
|
|
658
|
+
i.lastActor ?? "",
|
|
659
|
+
i.author ?? ""
|
|
660
|
+
].join("|");
|
|
661
|
+
}
|
|
662
|
+
if (item.kind === "jira") {
|
|
663
|
+
const j = item;
|
|
664
|
+
return [j.summary, j.jiraStatus].join("|");
|
|
665
|
+
}
|
|
666
|
+
return "";
|
|
667
|
+
};
|
|
668
|
+
var indexRows = (sections) => {
|
|
669
|
+
const out = /* @__PURE__ */ new Map();
|
|
670
|
+
for (const section of sections) {
|
|
671
|
+
section.items.forEach((item, index) => {
|
|
672
|
+
const key = keyOf(item);
|
|
673
|
+
if (key) out.set(key, { item, sectionId: section.id, index });
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
};
|
|
678
|
+
var spliceBack = (next, leaving) => {
|
|
679
|
+
if (leaving.length === 0) return next;
|
|
680
|
+
const bySection = /* @__PURE__ */ new Map();
|
|
681
|
+
for (const row of leaving) {
|
|
682
|
+
const list = bySection.get(row.sectionId);
|
|
683
|
+
if (list) list.push(row);
|
|
684
|
+
else bySection.set(row.sectionId, [row]);
|
|
685
|
+
}
|
|
686
|
+
return next.map((section) => {
|
|
687
|
+
const rows = bySection.get(section.id);
|
|
688
|
+
if (!rows) return section;
|
|
689
|
+
const items = [...section.items];
|
|
690
|
+
for (const row of [...rows].sort((a, b) => a.index - b.index))
|
|
691
|
+
items.splice(Math.min(row.index, items.length), 0, row.item);
|
|
692
|
+
return { ...section, items };
|
|
693
|
+
});
|
|
694
|
+
};
|
|
695
|
+
var diffSections = (prev, next) => {
|
|
696
|
+
const before = indexRows(prev);
|
|
697
|
+
const after = indexRows(next);
|
|
698
|
+
const transients = /* @__PURE__ */ new Map();
|
|
699
|
+
const leaving = [];
|
|
700
|
+
for (const [key] of after) if (!before.has(key)) transients.set(key, "in");
|
|
701
|
+
for (const [key, was] of before) {
|
|
702
|
+
const now = after.get(key);
|
|
703
|
+
if (!now) {
|
|
704
|
+
transients.set(key, "out");
|
|
705
|
+
leaving.push(was);
|
|
706
|
+
} else if (renderedState(was.item) !== renderedState(now.item)) {
|
|
707
|
+
transients.set(key, "changed");
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
let added = 0;
|
|
711
|
+
let removed = 0;
|
|
712
|
+
let changed = 0;
|
|
713
|
+
for (const [, kind] of transients) {
|
|
714
|
+
if (kind === "in") added++;
|
|
715
|
+
else if (kind === "out") removed++;
|
|
716
|
+
else changed++;
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
transients,
|
|
720
|
+
union: spliceBack(next, leaving),
|
|
721
|
+
counts: { added, removed, changed }
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
var summariseDiff = (counts) => {
|
|
725
|
+
const parts = [];
|
|
726
|
+
if (counts.added) parts.push(`${counts.added} new`);
|
|
727
|
+
if (counts.removed) parts.push(`${counts.removed} gone`);
|
|
728
|
+
if (counts.changed) parts.push(`${counts.changed} moved`);
|
|
729
|
+
return parts.join(" \xB7 ");
|
|
730
|
+
};
|
|
731
|
+
var transientOf = (transients, item) => {
|
|
732
|
+
if (!transients?.size) return void 0;
|
|
733
|
+
const key = keyOf(item);
|
|
734
|
+
return key ? transients.get(key) : void 0;
|
|
735
|
+
};
|
|
640
736
|
var quietly = $({ quiet: true });
|
|
641
737
|
var relativeTime = (iso) => {
|
|
642
738
|
const diff = (Date.now() - new Date(iso).getTime()) / 1e3;
|
|
@@ -657,15 +753,24 @@ var healthSentence = (item) => {
|
|
|
657
753
|
case "draft":
|
|
658
754
|
return `Still a draft (${glyph}), so no review is being asked for yet.`;
|
|
659
755
|
case "ci-fail":
|
|
660
|
-
return `CI is failing (${glyph}) \u2014 ${plural(
|
|
756
|
+
return `CI is failing (${glyph}) \u2014 ${plural(
|
|
757
|
+
d?.checksFail ?? 0,
|
|
758
|
+
"check"
|
|
759
|
+
)} red.`;
|
|
661
760
|
case "conflict":
|
|
662
761
|
return `It conflicts with the base branch (${glyph}) and cannot merge until that is resolved.`;
|
|
663
762
|
case "changes-req":
|
|
664
763
|
return `Changes were requested (${glyph}).`;
|
|
665
764
|
case "threads":
|
|
666
|
-
return `${plural(
|
|
765
|
+
return `${plural(
|
|
766
|
+
item.unresolved,
|
|
767
|
+
"review thread"
|
|
768
|
+
)} still open (${glyph}).`;
|
|
667
769
|
case "pending":
|
|
668
|
-
return `${plural(
|
|
770
|
+
return `${plural(
|
|
771
|
+
d?.checksPending ?? 0,
|
|
772
|
+
"check"
|
|
773
|
+
)} still running (${glyph}).`;
|
|
669
774
|
case "approved":
|
|
670
775
|
return `Approved (${glyph}) and ready to merge.`;
|
|
671
776
|
case "waiting":
|
|
@@ -698,7 +803,9 @@ var turnSentences = (item, login) => {
|
|
|
698
803
|
const lines = [`You spoke last (\u2192), ${when}. The ball is with ${them}.`];
|
|
699
804
|
if (d?.lastCommitAt && d.lastEventAt && d.lastCommitAt < d.lastEventAt)
|
|
700
805
|
lines.push(
|
|
701
|
-
`Nothing has been pushed since ${relativeTime(
|
|
806
|
+
`Nothing has been pushed since ${relativeTime(
|
|
807
|
+
d.lastCommitAt
|
|
808
|
+
)} ago, so it is stalled on ${them}, not on you.`
|
|
702
809
|
);
|
|
703
810
|
return lines;
|
|
704
811
|
};
|
|
@@ -1370,13 +1477,14 @@ var InboxHeader = ({
|
|
|
1370
1477
|
quiet,
|
|
1371
1478
|
refreshing,
|
|
1372
1479
|
hasPending,
|
|
1480
|
+
pendingSummary,
|
|
1373
1481
|
fetchedAt
|
|
1374
1482
|
}) => {
|
|
1375
1483
|
const total = sections.reduce((n, s) => n + topLevelCount(s), 0);
|
|
1376
1484
|
const countSeg = loading ? " loading\u2026 " : quiet ? " " : ` ${String(total).padStart(3)} item${total !== 1 ? "s" : ""} \xB7 `;
|
|
1377
1485
|
const userSeg = loading || quiet ? "" : `@${login} `;
|
|
1378
1486
|
const workLabel = work === void 0 ? "" : " w work \u25CF\u2500\u25CB home ";
|
|
1379
|
-
const [statusText, statusColor] = hasPending ? [
|
|
1487
|
+
const [statusText, statusColor] = hasPending ? [`\u25CF ${pendingSummary || "new"} \xB7 r apply`, "#FF8700"] : refreshing ? ["\u21BB refreshing\u2026", "cyan"] : fetchedAt ? [`updated ${agoText(fetchedAt)}`, void 0] : ["", void 0];
|
|
1380
1488
|
const statusSeg = statusText ? statusText + " " : "";
|
|
1381
1489
|
const fill = Math.max(
|
|
1382
1490
|
4,
|
|
@@ -1452,12 +1560,27 @@ var MERGED_HOLD_MS = 3e3;
|
|
|
1452
1560
|
var MERGED_FRAME_MS = 150;
|
|
1453
1561
|
var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
|
|
1454
1562
|
var MERGED_COLOUR = "#A371F7";
|
|
1563
|
+
var TRANSIT_HOLD_MS = 2500;
|
|
1564
|
+
var NO_TRANSIENTS = /* @__PURE__ */ new Map();
|
|
1565
|
+
var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
|
|
1566
|
+
var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
1567
|
+
var TRANSIT_COLOUR = {
|
|
1568
|
+
in: "#3FB950",
|
|
1569
|
+
out: "#8B949E",
|
|
1570
|
+
changed: "#FF8700"
|
|
1571
|
+
};
|
|
1572
|
+
var TRANSIT_LABEL = {
|
|
1573
|
+
in: "NEW",
|
|
1574
|
+
out: "GONE",
|
|
1575
|
+
changed: "UPDATED"
|
|
1576
|
+
};
|
|
1455
1577
|
var ItemRow = ({
|
|
1456
1578
|
item,
|
|
1457
1579
|
active,
|
|
1458
1580
|
gap,
|
|
1459
1581
|
login,
|
|
1460
1582
|
merged,
|
|
1583
|
+
transient,
|
|
1461
1584
|
sparkFrame = 0
|
|
1462
1585
|
}) => {
|
|
1463
1586
|
if (item.kind === "repo-header")
|
|
@@ -1482,27 +1605,31 @@ var ItemRow = ({
|
|
|
1482
1605
|
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: "show less" })
|
|
1483
1606
|
] });
|
|
1484
1607
|
if (item.kind === "jira") {
|
|
1485
|
-
const
|
|
1608
|
+
const note = item.note ?? "";
|
|
1609
|
+
const titleMax2 = Math.max(20, COLS - item.key.length - note.length - 10);
|
|
1486
1610
|
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
|
|
1487
1611
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
|
|
1488
1612
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
|
|
1489
|
-
/* @__PURE__ */ jsx(Text2, { bold: active, children: truncate(item.summary, titleMax2) })
|
|
1613
|
+
/* @__PURE__ */ jsx(Text2, { bold: active, children: truncate(item.summary, titleMax2) }),
|
|
1614
|
+
note ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${note}` }) : null
|
|
1490
1615
|
] });
|
|
1491
1616
|
}
|
|
1492
1617
|
const { glyph: healthIcon, color: healthColor2 } = healthDisplay[item.health];
|
|
1493
|
-
const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : healthIcon;
|
|
1494
|
-
const color = merged ? MERGED_COLOUR : healthColor2;
|
|
1618
|
+
const icon = merged ? MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length] : transient === "out" ? TRANSIT_OUT_FRAMES[sparkFrame % TRANSIT_OUT_FRAMES.length] : transient === "in" ? TRANSIT_IN_FRAMES[sparkFrame % TRANSIT_IN_FRAMES.length] : healthIcon;
|
|
1619
|
+
const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
|
|
1495
1620
|
const [turnIcon, turnColor] = !login || !item.lastActor ? [" ", "white"] : item.lastActor === login ? ["\u2192", "#888888"] : ["\u2190", "#FF8700"];
|
|
1496
1621
|
const numStr = `#${item.number}`.padEnd(7);
|
|
1497
1622
|
const showAuthor = !!item.author && item.author !== login;
|
|
1498
1623
|
const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
|
|
1499
1624
|
const ageLabel = item.activityAge && item.activityAge !== item.age ? `${item.activityAge} \xB7 ${item.age}` : item.age;
|
|
1500
1625
|
const mergedLabel = merged ? "MERGED" : "";
|
|
1626
|
+
const transitLabel = merged || !transient ? "" : TRANSIT_LABEL[transient];
|
|
1501
1627
|
const suffix = [
|
|
1502
1628
|
ageLabel || "",
|
|
1503
1629
|
unresolvedLabel,
|
|
1504
1630
|
showAuthor ? `by ${item.author}` : "",
|
|
1505
|
-
mergedLabel
|
|
1631
|
+
mergedLabel,
|
|
1632
|
+
transitLabel
|
|
1506
1633
|
].filter(Boolean).join(" ");
|
|
1507
1634
|
const repoLabel = item.indent ? item.repo : "";
|
|
1508
1635
|
const fixedWidth = 2 + (item.indent ? 3 : 0) + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
|
|
@@ -1513,12 +1640,21 @@ var ItemRow = ({
|
|
|
1513
1640
|
/* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
|
|
1514
1641
|
/* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
|
|
1515
1642
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
|
|
1516
|
-
/* @__PURE__ */ jsx(
|
|
1643
|
+
/* @__PURE__ */ jsx(
|
|
1644
|
+
Text2,
|
|
1645
|
+
{
|
|
1646
|
+
bold: active || transient === "in",
|
|
1647
|
+
dimColor: transient === "out",
|
|
1648
|
+
strikethrough: transient === "out",
|
|
1649
|
+
children: truncate(item.title, titleMax) + " "
|
|
1650
|
+
}
|
|
1651
|
+
),
|
|
1517
1652
|
repoLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: repoLabel }) : null,
|
|
1518
1653
|
unresolvedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: "#FF8700", children: " " + unresolvedLabel }) : null,
|
|
1519
1654
|
showAuthor ? /* @__PURE__ */ jsx(Text2, { dimColor: true, italic: true, children: " by " + item.author }) : null,
|
|
1520
1655
|
ageLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + ageLabel }) : null,
|
|
1521
|
-
mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null
|
|
1656
|
+
mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null,
|
|
1657
|
+
transitLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel }) : null
|
|
1522
1658
|
] });
|
|
1523
1659
|
};
|
|
1524
1660
|
var useActionMenu = () => {
|
|
@@ -1794,6 +1930,7 @@ var BrowseScreen = ({
|
|
|
1794
1930
|
onActed,
|
|
1795
1931
|
refreshing,
|
|
1796
1932
|
hasPending,
|
|
1933
|
+
pendingSummary,
|
|
1797
1934
|
fetchedAt,
|
|
1798
1935
|
refreshError,
|
|
1799
1936
|
workToggle,
|
|
@@ -1808,7 +1945,8 @@ var BrowseScreen = ({
|
|
|
1808
1945
|
isWorkRepo,
|
|
1809
1946
|
initialIncludeWork,
|
|
1810
1947
|
brand,
|
|
1811
|
-
mergedUrls
|
|
1948
|
+
mergedUrls,
|
|
1949
|
+
transients
|
|
1812
1950
|
}) => {
|
|
1813
1951
|
const { rows } = useWindowSize();
|
|
1814
1952
|
const [includeWork, setIncludeWork] = useState(
|
|
@@ -1899,7 +2037,7 @@ var BrowseScreen = ({
|
|
|
1899
2037
|
setTimeout(() => setFlash(null), 2e3);
|
|
1900
2038
|
};
|
|
1901
2039
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
1902
|
-
const sparkling = (mergedUrls?.length ?? 0) > 0;
|
|
2040
|
+
const sparkling = (mergedUrls?.length ?? 0) > 0 || (transients?.size ?? 0) > 0;
|
|
1903
2041
|
useEffect(() => {
|
|
1904
2042
|
if (!sparkling) return;
|
|
1905
2043
|
const id = setInterval(() => setSparkFrame((f) => f + 1), MERGED_FRAME_MS);
|
|
@@ -2231,6 +2369,7 @@ var BrowseScreen = ({
|
|
|
2231
2369
|
work: workToggle ? includeWork : void 0,
|
|
2232
2370
|
refreshing,
|
|
2233
2371
|
hasPending,
|
|
2372
|
+
pendingSummary,
|
|
2234
2373
|
fetchedAt
|
|
2235
2374
|
}
|
|
2236
2375
|
),
|
|
@@ -2250,13 +2389,7 @@ var BrowseScreen = ({
|
|
|
2250
2389
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: " / " }),
|
|
2251
2390
|
/* @__PURE__ */ jsx(Text2, { children: search }),
|
|
2252
2391
|
searchInput ? /* @__PURE__ */ jsx(Text2, { color: "cyan", children: "\u258F" }) : null,
|
|
2253
|
-
/* @__PURE__ */ jsx(
|
|
2254
|
-
Text2,
|
|
2255
|
-
{
|
|
2256
|
-
dimColor: true,
|
|
2257
|
-
children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}`
|
|
2258
|
-
}
|
|
2259
|
-
)
|
|
2392
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
|
|
2260
2393
|
] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
2261
2394
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " \u25C9 " }),
|
|
2262
2395
|
/* @__PURE__ */ jsx(Text2, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
|
|
@@ -2271,6 +2404,7 @@ var BrowseScreen = ({
|
|
|
2271
2404
|
active: viewStart + i === cursor,
|
|
2272
2405
|
login,
|
|
2273
2406
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
2407
|
+
transient: transientOf(transients, item),
|
|
2274
2408
|
sparkFrame,
|
|
2275
2409
|
gap: (
|
|
2276
2410
|
// Window-relative, never `viewStart + i`. fitCount prices the
|
|
@@ -2353,6 +2487,8 @@ var App = ({
|
|
|
2353
2487
|
ciJob,
|
|
2354
2488
|
ciFetcher,
|
|
2355
2489
|
ciPollMs = 6e4,
|
|
2490
|
+
watchPath,
|
|
2491
|
+
watchDebounceMs = 400,
|
|
2356
2492
|
extensions,
|
|
2357
2493
|
tabHelp,
|
|
2358
2494
|
emptyHint
|
|
@@ -2370,12 +2506,47 @@ var App = ({
|
|
|
2370
2506
|
return sameCiStatusState(prev, next) ? prev : next;
|
|
2371
2507
|
});
|
|
2372
2508
|
const displayedKey = useRef("");
|
|
2509
|
+
const displayedSections = useRef([]);
|
|
2510
|
+
const [transients, setTransients] = useState(NO_TRANSIENTS);
|
|
2511
|
+
const transitTimer = useRef(null);
|
|
2512
|
+
useEffect(
|
|
2513
|
+
() => () => {
|
|
2514
|
+
if (transitTimer.current) clearTimeout(transitTimer.current);
|
|
2515
|
+
},
|
|
2516
|
+
[]
|
|
2517
|
+
);
|
|
2373
2518
|
const showData = (sections, login) => {
|
|
2519
|
+
const before = displayedSections.current;
|
|
2520
|
+
displayedSections.current = sections;
|
|
2374
2521
|
displayedKey.current = signatureOf(sections);
|
|
2375
2522
|
setPending(null);
|
|
2376
2523
|
setFetchedAt(Date.now());
|
|
2377
|
-
|
|
2524
|
+
if (before.length === 0) {
|
|
2525
|
+
setTransients(NO_TRANSIENTS);
|
|
2526
|
+
setState({ phase: "browse", sections, login });
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
const { transients: marks, union } = diffSections(before, sections);
|
|
2530
|
+
if (marks.size === 0) {
|
|
2531
|
+
setState({ phase: "browse", sections, login });
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
setTransients(marks);
|
|
2535
|
+
setState({ phase: "browse", sections: union, login });
|
|
2536
|
+
if (transitTimer.current) clearTimeout(transitTimer.current);
|
|
2537
|
+
transitTimer.current = setTimeout(() => {
|
|
2538
|
+
setTransients(NO_TRANSIENTS);
|
|
2539
|
+
setState(
|
|
2540
|
+
(prev) => prev.phase === "browse" && prev.sections === union ? { ...prev, sections } : prev
|
|
2541
|
+
);
|
|
2542
|
+
}, TRANSIT_HOLD_MS);
|
|
2378
2543
|
};
|
|
2544
|
+
const pendingSummary = useMemo(
|
|
2545
|
+
() => pending ? summariseDiff(
|
|
2546
|
+
diffSections(displayedSections.current, pending.sections).counts
|
|
2547
|
+
) : "",
|
|
2548
|
+
[pending]
|
|
2549
|
+
);
|
|
2379
2550
|
const revalidate = (manual = false) => {
|
|
2380
2551
|
if (manual) setRefreshing(true);
|
|
2381
2552
|
fetcher().then((fresh) => {
|
|
@@ -2419,6 +2590,7 @@ var App = ({
|
|
|
2419
2590
|
const painted = !!cached && cached.sections.length > 0;
|
|
2420
2591
|
if (painted && cached) {
|
|
2421
2592
|
displayedKey.current = signatureOf(cached.sections);
|
|
2593
|
+
displayedSections.current = cached.sections;
|
|
2422
2594
|
setFetchedAt(cached.at);
|
|
2423
2595
|
setState({
|
|
2424
2596
|
phase: "browse",
|
|
@@ -2445,6 +2617,29 @@ var App = ({
|
|
|
2445
2617
|
clearInterval(id);
|
|
2446
2618
|
};
|
|
2447
2619
|
}, [hasCiStatus, ciFetcher, ciPollMs]);
|
|
2620
|
+
useEffect(() => {
|
|
2621
|
+
if (!watchPath) return;
|
|
2622
|
+
const dir = dirname(watchPath);
|
|
2623
|
+
const name = basename(watchPath);
|
|
2624
|
+
if (!existsSync(dir)) return;
|
|
2625
|
+
let timer = null;
|
|
2626
|
+
let live = true;
|
|
2627
|
+
let watcher;
|
|
2628
|
+
try {
|
|
2629
|
+
watcher = watch(dir, (_event, changed) => {
|
|
2630
|
+
if (!live || changed && changed !== name) return;
|
|
2631
|
+
if (timer) clearTimeout(timer);
|
|
2632
|
+
timer = setTimeout(() => live && revalidate(), watchDebounceMs);
|
|
2633
|
+
});
|
|
2634
|
+
} catch {
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
return () => {
|
|
2638
|
+
live = false;
|
|
2639
|
+
if (timer) clearTimeout(timer);
|
|
2640
|
+
watcher.close();
|
|
2641
|
+
};
|
|
2642
|
+
}, [watchPath, watchDebounceMs]);
|
|
2448
2643
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
2449
2644
|
const mergeTimers = useRef([]);
|
|
2450
2645
|
useEffect(() => () => mergeTimers.current.forEach(clearTimeout), []);
|
|
@@ -2521,6 +2716,7 @@ var App = ({
|
|
|
2521
2716
|
onActed,
|
|
2522
2717
|
refreshing,
|
|
2523
2718
|
hasPending: pending !== null,
|
|
2719
|
+
pendingSummary,
|
|
2524
2720
|
fetchedAt,
|
|
2525
2721
|
refreshError: refreshError ?? void 0,
|
|
2526
2722
|
workToggle,
|
|
@@ -2528,6 +2724,7 @@ var App = ({
|
|
|
2528
2724
|
initialIncludeWork,
|
|
2529
2725
|
hidden: overlay !== null,
|
|
2530
2726
|
mergedUrls,
|
|
2727
|
+
transients,
|
|
2531
2728
|
ciStatusState: hasCiStatus ? ciStatusState : void 0,
|
|
2532
2729
|
ciJob,
|
|
2533
2730
|
tabHelp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|