@kud/gh-ink 0.11.1 → 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 +218 -23
- 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,6 +2402,7 @@ 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
2407
|
gap: (
|
|
2276
2408
|
// Window-relative, never `viewStart + i`. fitCount prices the
|
|
@@ -2353,6 +2485,8 @@ var App = ({
|
|
|
2353
2485
|
ciJob,
|
|
2354
2486
|
ciFetcher,
|
|
2355
2487
|
ciPollMs = 6e4,
|
|
2488
|
+
watchPath,
|
|
2489
|
+
watchDebounceMs = 400,
|
|
2356
2490
|
extensions,
|
|
2357
2491
|
tabHelp,
|
|
2358
2492
|
emptyHint
|
|
@@ -2370,12 +2504,47 @@ var App = ({
|
|
|
2370
2504
|
return sameCiStatusState(prev, next) ? prev : next;
|
|
2371
2505
|
});
|
|
2372
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
|
+
);
|
|
2373
2516
|
const showData = (sections, login) => {
|
|
2517
|
+
const before = displayedSections.current;
|
|
2518
|
+
displayedSections.current = sections;
|
|
2374
2519
|
displayedKey.current = signatureOf(sections);
|
|
2375
2520
|
setPending(null);
|
|
2376
2521
|
setFetchedAt(Date.now());
|
|
2377
|
-
|
|
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);
|
|
2378
2541
|
};
|
|
2542
|
+
const pendingSummary = useMemo(
|
|
2543
|
+
() => pending ? summariseDiff(
|
|
2544
|
+
diffSections(displayedSections.current, pending.sections).counts
|
|
2545
|
+
) : "",
|
|
2546
|
+
[pending]
|
|
2547
|
+
);
|
|
2379
2548
|
const revalidate = (manual = false) => {
|
|
2380
2549
|
if (manual) setRefreshing(true);
|
|
2381
2550
|
fetcher().then((fresh) => {
|
|
@@ -2419,6 +2588,7 @@ var App = ({
|
|
|
2419
2588
|
const painted = !!cached && cached.sections.length > 0;
|
|
2420
2589
|
if (painted && cached) {
|
|
2421
2590
|
displayedKey.current = signatureOf(cached.sections);
|
|
2591
|
+
displayedSections.current = cached.sections;
|
|
2422
2592
|
setFetchedAt(cached.at);
|
|
2423
2593
|
setState({
|
|
2424
2594
|
phase: "browse",
|
|
@@ -2445,6 +2615,29 @@ var App = ({
|
|
|
2445
2615
|
clearInterval(id);
|
|
2446
2616
|
};
|
|
2447
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]);
|
|
2448
2641
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
2449
2642
|
const mergeTimers = useRef([]);
|
|
2450
2643
|
useEffect(() => () => mergeTimers.current.forEach(clearTimeout), []);
|
|
@@ -2521,6 +2714,7 @@ var App = ({
|
|
|
2521
2714
|
onActed,
|
|
2522
2715
|
refreshing,
|
|
2523
2716
|
hasPending: pending !== null,
|
|
2717
|
+
pendingSummary,
|
|
2524
2718
|
fetchedAt,
|
|
2525
2719
|
refreshError: refreshError ?? void 0,
|
|
2526
2720
|
workToggle,
|
|
@@ -2528,6 +2722,7 @@ var App = ({
|
|
|
2528
2722
|
initialIncludeWork,
|
|
2529
2723
|
hidden: overlay !== null,
|
|
2530
2724
|
mergedUrls,
|
|
2725
|
+
transients,
|
|
2531
2726
|
ciStatusState: hasCiStatus ? ciStatusState : void 0,
|
|
2532
2727
|
ciJob,
|
|
2533
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",
|