@kud/gh-ink 0.11.0 → 0.12.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 +9 -1
- package/dist/index.js +234 -32
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -225,7 +225,7 @@ declare const ActionMenu: ({ item, actions, cursor, }: {
|
|
|
225
225
|
actions: Action[];
|
|
226
226
|
cursor: number;
|
|
227
227
|
}) => React.JSX.Element;
|
|
228
|
-
declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, extensions, tabHelp, emptyHint, }: {
|
|
228
|
+
declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIncludeWork, jiraBase, jiraKeyRe, jiraTransitions, workToggle, hasCiStatus, ciJob, ciFetcher, ciPollMs, watchPath, watchDebounceMs, extensions, tabHelp, emptyHint, }: {
|
|
229
229
|
fetcher: () => Promise<{
|
|
230
230
|
sections: Section[];
|
|
231
231
|
login: string;
|
|
@@ -246,6 +246,14 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, isWorkRepo, initialIn
|
|
|
246
246
|
ciJob?: string;
|
|
247
247
|
ciFetcher?: () => Promise<CiStatus | null>;
|
|
248
248
|
ciPollMs?: number;
|
|
249
|
+
/**
|
|
250
|
+
* A file something else touches when it has changed GitHub on your behalf —
|
|
251
|
+
* a Claude session closing an issue, a script merging a PR. Touching it makes
|
|
252
|
+
* the inbox refetch; it never repaints on its own.
|
|
253
|
+
*/
|
|
254
|
+
watchPath?: string;
|
|
255
|
+
/** Bursts of writes to collapse into one refetch. */
|
|
256
|
+
watchDebounceMs?: number;
|
|
249
257
|
extensions?: InboxExtension[];
|
|
250
258
|
}) => React.JSX.Element;
|
|
251
259
|
|
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")
|
|
@@ -1490,19 +1613,21 @@ var ItemRow = ({
|
|
|
1490
1613
|
] });
|
|
1491
1614
|
}
|
|
1492
1615
|
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;
|
|
1616
|
+
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;
|
|
1617
|
+
const color = merged ? MERGED_COLOUR : transient ? TRANSIT_COLOUR[transient] : healthColor2;
|
|
1495
1618
|
const [turnIcon, turnColor] = !login || !item.lastActor ? [" ", "white"] : item.lastActor === login ? ["\u2192", "#888888"] : ["\u2190", "#FF8700"];
|
|
1496
1619
|
const numStr = `#${item.number}`.padEnd(7);
|
|
1497
1620
|
const showAuthor = !!item.author && item.author !== login;
|
|
1498
1621
|
const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
|
|
1499
1622
|
const ageLabel = item.activityAge && item.activityAge !== item.age ? `${item.activityAge} \xB7 ${item.age}` : item.age;
|
|
1500
1623
|
const mergedLabel = merged ? "MERGED" : "";
|
|
1624
|
+
const transitLabel = merged || !transient ? "" : TRANSIT_LABEL[transient];
|
|
1501
1625
|
const suffix = [
|
|
1502
1626
|
ageLabel || "",
|
|
1503
1627
|
unresolvedLabel,
|
|
1504
1628
|
showAuthor ? `by ${item.author}` : "",
|
|
1505
|
-
mergedLabel
|
|
1629
|
+
mergedLabel,
|
|
1630
|
+
transitLabel
|
|
1506
1631
|
].filter(Boolean).join(" ");
|
|
1507
1632
|
const repoLabel = item.indent ? item.repo : "";
|
|
1508
1633
|
const fixedWidth = 2 + (item.indent ? 3 : 0) + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
|
|
@@ -1513,12 +1638,21 @@ var ItemRow = ({
|
|
|
1513
1638
|
/* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
|
|
1514
1639
|
/* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
|
|
1515
1640
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
|
|
1516
|
-
/* @__PURE__ */ jsx(
|
|
1641
|
+
/* @__PURE__ */ jsx(
|
|
1642
|
+
Text2,
|
|
1643
|
+
{
|
|
1644
|
+
bold: active || transient === "in",
|
|
1645
|
+
dimColor: transient === "out",
|
|
1646
|
+
strikethrough: transient === "out",
|
|
1647
|
+
children: truncate(item.title, titleMax) + " "
|
|
1648
|
+
}
|
|
1649
|
+
),
|
|
1517
1650
|
repoLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: repoLabel }) : null,
|
|
1518
1651
|
unresolvedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: "#FF8700", children: " " + unresolvedLabel }) : null,
|
|
1519
1652
|
showAuthor ? /* @__PURE__ */ jsx(Text2, { dimColor: true, italic: true, children: " by " + item.author }) : null,
|
|
1520
1653
|
ageLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + ageLabel }) : null,
|
|
1521
|
-
mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null
|
|
1654
|
+
mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null,
|
|
1655
|
+
transitLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: TRANSIT_COLOUR[transient], children: " " + transitLabel }) : null
|
|
1522
1656
|
] });
|
|
1523
1657
|
};
|
|
1524
1658
|
var useActionMenu = () => {
|
|
@@ -1794,6 +1928,7 @@ var BrowseScreen = ({
|
|
|
1794
1928
|
onActed,
|
|
1795
1929
|
refreshing,
|
|
1796
1930
|
hasPending,
|
|
1931
|
+
pendingSummary,
|
|
1797
1932
|
fetchedAt,
|
|
1798
1933
|
refreshError,
|
|
1799
1934
|
workToggle,
|
|
@@ -1808,7 +1943,8 @@ var BrowseScreen = ({
|
|
|
1808
1943
|
isWorkRepo,
|
|
1809
1944
|
initialIncludeWork,
|
|
1810
1945
|
brand,
|
|
1811
|
-
mergedUrls
|
|
1946
|
+
mergedUrls,
|
|
1947
|
+
transients
|
|
1812
1948
|
}) => {
|
|
1813
1949
|
const { rows } = useWindowSize();
|
|
1814
1950
|
const [includeWork, setIncludeWork] = useState(
|
|
@@ -1899,7 +2035,7 @@ var BrowseScreen = ({
|
|
|
1899
2035
|
setTimeout(() => setFlash(null), 2e3);
|
|
1900
2036
|
};
|
|
1901
2037
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
1902
|
-
const sparkling = (mergedUrls?.length ?? 0) > 0;
|
|
2038
|
+
const sparkling = (mergedUrls?.length ?? 0) > 0 || (transients?.size ?? 0) > 0;
|
|
1903
2039
|
useEffect(() => {
|
|
1904
2040
|
if (!sparkling) return;
|
|
1905
2041
|
const id = setInterval(() => setSparkFrame((f) => f + 1), MERGED_FRAME_MS);
|
|
@@ -2231,6 +2367,7 @@ var BrowseScreen = ({
|
|
|
2231
2367
|
work: workToggle ? includeWork : void 0,
|
|
2232
2368
|
refreshing,
|
|
2233
2369
|
hasPending,
|
|
2370
|
+
pendingSummary,
|
|
2234
2371
|
fetchedAt
|
|
2235
2372
|
}
|
|
2236
2373
|
),
|
|
@@ -2250,13 +2387,7 @@ var BrowseScreen = ({
|
|
|
2250
2387
|
/* @__PURE__ */ jsx(Text2, { color: "cyan", children: " / " }),
|
|
2251
2388
|
/* @__PURE__ */ jsx(Text2, { children: search }),
|
|
2252
2389
|
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
|
-
)
|
|
2390
|
+
/* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${matchCount} match${matchCount !== 1 ? "es" : ""}${searchInput ? " \u21B5 accept \xB7 esc clear" : " esc clear"}` })
|
|
2260
2391
|
] }) : repoFilter.size > 0 ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
2261
2392
|
/* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: " \u25C9 " }),
|
|
2262
2393
|
/* @__PURE__ */ jsx(Text2, { children: `${repoFilter.size} repo${repoFilter.size !== 1 ? "s" : ""}` }),
|
|
@@ -2271,16 +2402,24 @@ var BrowseScreen = ({
|
|
|
2271
2402
|
active: viewStart + i === cursor,
|
|
2272
2403
|
login,
|
|
2273
2404
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
2405
|
+
transient: transientOf(transients, item),
|
|
2274
2406
|
sparkFrame,
|
|
2275
|
-
gap:
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2407
|
+
gap: (
|
|
2408
|
+
// Window-relative, never `viewStart + i`. fitCount prices the
|
|
2409
|
+
// window's FIRST row at one line (isFirst), so gapping it when
|
|
2410
|
+
// scrolled draws a row the budget never bought — and the frame
|
|
2411
|
+
// is sized to fill the terminal exactly, so the overflow scrolls
|
|
2412
|
+
// the whole panel up a line instead of clipping.
|
|
2413
|
+
i > 0 && (item.kind === "repo-header" || item.kind === "subgroup-header" || // A header is always followed by a blank line before its
|
|
2414
|
+
// first child — in Other PRs that's "free" because the
|
|
2415
|
+
// child is itself a repo-header (gap above). A jira row
|
|
2416
|
+
// has no such stand-in, so it needs this explicitly. Never
|
|
2417
|
+
// applies between two tickets/PRs — only right after a
|
|
2418
|
+
// header.
|
|
2419
|
+
item.kind === "jira" && ["repo-header", "subgroup-header"].includes(
|
|
2420
|
+
section.items[viewStart + i - 1]?.kind
|
|
2421
|
+
))
|
|
2422
|
+
)
|
|
2284
2423
|
},
|
|
2285
2424
|
`${viewStart + i}:${item.kind === "jira" ? item.instanceKey ?? item.key : item.kind === "repo-header" ? `header:${item.repo}` : item.kind === "subgroup-header" ? `subgroup:${item.label}` : item.kind === "show-more" ? `show-more:${item.hidden[0]?.repo ?? i}` : item.kind === "show-less" ? `show-less:${item.toHide[0]?.repo ?? i}` : `${item.repo}/${item.number}`}`
|
|
2286
2425
|
)) }),
|
|
@@ -2346,6 +2485,8 @@ var App = ({
|
|
|
2346
2485
|
ciJob,
|
|
2347
2486
|
ciFetcher,
|
|
2348
2487
|
ciPollMs = 6e4,
|
|
2488
|
+
watchPath,
|
|
2489
|
+
watchDebounceMs = 400,
|
|
2349
2490
|
extensions,
|
|
2350
2491
|
tabHelp,
|
|
2351
2492
|
emptyHint
|
|
@@ -2363,12 +2504,47 @@ var App = ({
|
|
|
2363
2504
|
return sameCiStatusState(prev, next) ? prev : next;
|
|
2364
2505
|
});
|
|
2365
2506
|
const displayedKey = useRef("");
|
|
2507
|
+
const displayedSections = useRef([]);
|
|
2508
|
+
const [transients, setTransients] = useState(NO_TRANSIENTS);
|
|
2509
|
+
const transitTimer = useRef(null);
|
|
2510
|
+
useEffect(
|
|
2511
|
+
() => () => {
|
|
2512
|
+
if (transitTimer.current) clearTimeout(transitTimer.current);
|
|
2513
|
+
},
|
|
2514
|
+
[]
|
|
2515
|
+
);
|
|
2366
2516
|
const showData = (sections, login) => {
|
|
2517
|
+
const before = displayedSections.current;
|
|
2518
|
+
displayedSections.current = sections;
|
|
2367
2519
|
displayedKey.current = signatureOf(sections);
|
|
2368
2520
|
setPending(null);
|
|
2369
2521
|
setFetchedAt(Date.now());
|
|
2370
|
-
|
|
2522
|
+
if (before.length === 0) {
|
|
2523
|
+
setTransients(NO_TRANSIENTS);
|
|
2524
|
+
setState({ phase: "browse", sections, login });
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const { transients: marks, union } = diffSections(before, sections);
|
|
2528
|
+
if (marks.size === 0) {
|
|
2529
|
+
setState({ phase: "browse", sections, login });
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
setTransients(marks);
|
|
2533
|
+
setState({ phase: "browse", sections: union, login });
|
|
2534
|
+
if (transitTimer.current) clearTimeout(transitTimer.current);
|
|
2535
|
+
transitTimer.current = setTimeout(() => {
|
|
2536
|
+
setTransients(NO_TRANSIENTS);
|
|
2537
|
+
setState(
|
|
2538
|
+
(prev) => prev.phase === "browse" && prev.sections === union ? { ...prev, sections } : prev
|
|
2539
|
+
);
|
|
2540
|
+
}, TRANSIT_HOLD_MS);
|
|
2371
2541
|
};
|
|
2542
|
+
const pendingSummary = useMemo(
|
|
2543
|
+
() => pending ? summariseDiff(
|
|
2544
|
+
diffSections(displayedSections.current, pending.sections).counts
|
|
2545
|
+
) : "",
|
|
2546
|
+
[pending]
|
|
2547
|
+
);
|
|
2372
2548
|
const revalidate = (manual = false) => {
|
|
2373
2549
|
if (manual) setRefreshing(true);
|
|
2374
2550
|
fetcher().then((fresh) => {
|
|
@@ -2412,6 +2588,7 @@ var App = ({
|
|
|
2412
2588
|
const painted = !!cached && cached.sections.length > 0;
|
|
2413
2589
|
if (painted && cached) {
|
|
2414
2590
|
displayedKey.current = signatureOf(cached.sections);
|
|
2591
|
+
displayedSections.current = cached.sections;
|
|
2415
2592
|
setFetchedAt(cached.at);
|
|
2416
2593
|
setState({
|
|
2417
2594
|
phase: "browse",
|
|
@@ -2438,6 +2615,29 @@ var App = ({
|
|
|
2438
2615
|
clearInterval(id);
|
|
2439
2616
|
};
|
|
2440
2617
|
}, [hasCiStatus, ciFetcher, ciPollMs]);
|
|
2618
|
+
useEffect(() => {
|
|
2619
|
+
if (!watchPath) return;
|
|
2620
|
+
const dir = dirname(watchPath);
|
|
2621
|
+
const name = basename(watchPath);
|
|
2622
|
+
if (!existsSync(dir)) return;
|
|
2623
|
+
let timer = null;
|
|
2624
|
+
let live = true;
|
|
2625
|
+
let watcher;
|
|
2626
|
+
try {
|
|
2627
|
+
watcher = watch(dir, (_event, changed) => {
|
|
2628
|
+
if (!live || changed && changed !== name) return;
|
|
2629
|
+
if (timer) clearTimeout(timer);
|
|
2630
|
+
timer = setTimeout(() => live && revalidate(), watchDebounceMs);
|
|
2631
|
+
});
|
|
2632
|
+
} catch {
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
return () => {
|
|
2636
|
+
live = false;
|
|
2637
|
+
if (timer) clearTimeout(timer);
|
|
2638
|
+
watcher.close();
|
|
2639
|
+
};
|
|
2640
|
+
}, [watchPath, watchDebounceMs]);
|
|
2441
2641
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
2442
2642
|
const mergeTimers = useRef([]);
|
|
2443
2643
|
useEffect(() => () => mergeTimers.current.forEach(clearTimeout), []);
|
|
@@ -2514,6 +2714,7 @@ var App = ({
|
|
|
2514
2714
|
onActed,
|
|
2515
2715
|
refreshing,
|
|
2516
2716
|
hasPending: pending !== null,
|
|
2717
|
+
pendingSummary,
|
|
2517
2718
|
fetchedAt,
|
|
2518
2719
|
refreshError: refreshError ?? void 0,
|
|
2519
2720
|
workToggle,
|
|
@@ -2521,6 +2722,7 @@ var App = ({
|
|
|
2521
2722
|
initialIncludeWork,
|
|
2522
2723
|
hidden: overlay !== null,
|
|
2523
2724
|
mergedUrls,
|
|
2725
|
+
transients,
|
|
2524
2726
|
ciStatusState: hasCiStatus ? ciStatusState : void 0,
|
|
2525
2727
|
ciJob,
|
|
2526
2728
|
tabHelp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|