@kud/gh-ink 0.10.0 → 0.11.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 CHANGED
@@ -20,8 +20,9 @@ type HealthPanelProps = {
20
20
  error: string | null;
21
21
  reload: () => void;
22
22
  onOpenCheck: (check: PrCheck) => void;
23
+ onMerged?: () => void;
23
24
  };
24
- declare const HealthPanel: ({ repo, number, data, error, reload, onOpenCheck, }: HealthPanelProps) => React.JSX.Element;
25
+ declare const HealthPanel: ({ repo, number, data, error, reload, onOpenCheck, onMerged, }: HealthPanelProps) => React.JSX.Element;
25
26
 
26
27
  declare const renderMarkdown: (raw: string, width: number, fileLink?: (path: string, line?: number) => string) => StyledLine[];
27
28
 
@@ -128,6 +129,12 @@ type DetailContext = {
128
129
  onBack: () => void;
129
130
  onRefresh: () => void;
130
131
  onRemove: (item: GHItem) => void;
132
+ /**
133
+ * The PR just merged from here. Returns to the list, marks the row merged for
134
+ * a few seconds, then drops it — distinct from onRemove, which drops it at
135
+ * once. Optional so a host that cannot merge need not implement it.
136
+ */
137
+ onMerged?: (item: GHItem) => void;
131
138
  };
132
139
  type Action = {
133
140
  label: string;
package/dist/index.js CHANGED
@@ -452,7 +452,8 @@ var HealthPanel = ({
452
452
  data,
453
453
  error,
454
454
  reload,
455
- onOpenCheck
455
+ onOpenCheck,
456
+ onMerged
456
457
  }) => {
457
458
  const [cursor, setCursor] = useState(0);
458
459
  const [note, setNote] = useState(null);
@@ -475,13 +476,14 @@ var HealthPanel = ({
475
476
  setNote(msg);
476
477
  setTimeout(() => setNote(null), 2500);
477
478
  };
478
- const act = async (msg, run) => {
479
+ const act = async (msg, run, onSuccess) => {
479
480
  setBusy(true);
480
481
  setNote(`\u22EF ${msg}\u2026`);
481
482
  try {
482
483
  await run();
483
484
  flash(`\u2713 ${msg}`);
484
- reload();
485
+ if (onSuccess) onSuccess();
486
+ else reload();
485
487
  } catch {
486
488
  flash(`\u2717 ${msg} failed`);
487
489
  } finally {
@@ -498,7 +500,7 @@ var HealthPanel = ({
498
500
  if (busy) return;
499
501
  if (confirm === "merge") {
500
502
  if (input === "y" || input === "Y")
501
- void act("merge", () => mergePr(repo, number));
503
+ void act("merge", () => mergePr(repo, number), onMerged);
502
504
  setConfirm(null);
503
505
  return;
504
506
  }
@@ -1446,11 +1448,17 @@ var RepoHeaderRow = ({ repo, gap }) => {
1446
1448
  const fill = Math.max(4, 46 - label.length);
1447
1449
  return /* @__PURE__ */ jsx(Box, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label + "\u2500".repeat(fill) }) });
1448
1450
  };
1451
+ var MERGED_HOLD_MS = 3e3;
1452
+ var MERGED_FRAME_MS = 150;
1453
+ var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
1454
+ var MERGED_COLOUR = "#A371F7";
1449
1455
  var ItemRow = ({
1450
1456
  item,
1451
1457
  active,
1452
1458
  gap,
1453
- login
1459
+ login,
1460
+ merged,
1461
+ sparkFrame = 0
1454
1462
  }) => {
1455
1463
  if (item.kind === "repo-header")
1456
1464
  return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false });
@@ -1481,16 +1489,20 @@ var ItemRow = ({
1481
1489
  /* @__PURE__ */ jsx(Text2, { bold: active, children: truncate(item.summary, titleMax2) })
1482
1490
  ] });
1483
1491
  }
1484
- const { glyph: icon, color } = healthDisplay[item.health];
1492
+ 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;
1485
1495
  const [turnIcon, turnColor] = !login || !item.lastActor ? [" ", "white"] : item.lastActor === login ? ["\u2192", "#888888"] : ["\u2190", "#FF8700"];
1486
1496
  const numStr = `#${item.number}`.padEnd(7);
1487
1497
  const showAuthor = !!item.author && item.author !== login;
1488
1498
  const unresolvedLabel = item.unresolved > 0 ? `\uF086 ${item.unresolved}` : "";
1489
1499
  const ageLabel = item.activityAge && item.activityAge !== item.age ? `${item.activityAge} \xB7 ${item.age}` : item.age;
1500
+ const mergedLabel = merged ? "MERGED" : "";
1490
1501
  const suffix = [
1491
1502
  ageLabel || "",
1492
1503
  unresolvedLabel,
1493
- showAuthor ? `by ${item.author}` : ""
1504
+ showAuthor ? `by ${item.author}` : "",
1505
+ mergedLabel
1494
1506
  ].filter(Boolean).join(" ");
1495
1507
  const repoLabel = item.indent ? item.repo : "";
1496
1508
  const fixedWidth = 2 + (item.indent ? 3 : 0) + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
@@ -1505,7 +1517,8 @@ var ItemRow = ({
1505
1517
  repoLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: repoLabel }) : null,
1506
1518
  unresolvedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: "#FF8700", children: " " + unresolvedLabel }) : null,
1507
1519
  showAuthor ? /* @__PURE__ */ jsx(Text2, { dimColor: true, italic: true, children: " by " + item.author }) : null,
1508
- ageLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + ageLabel }) : null
1520
+ ageLabel ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + ageLabel }) : null,
1521
+ mergedLabel ? /* @__PURE__ */ jsx(Text2, { bold: true, color: MERGED_COLOUR, children: " " + mergedLabel }) : null
1509
1522
  ] });
1510
1523
  };
1511
1524
  var useActionMenu = () => {
@@ -1794,7 +1807,8 @@ var BrowseScreen = ({
1794
1807
  tabHelp,
1795
1808
  isWorkRepo,
1796
1809
  initialIncludeWork,
1797
- brand
1810
+ brand,
1811
+ mergedUrls
1798
1812
  }) => {
1799
1813
  const { rows } = useWindowSize();
1800
1814
  const [includeWork, setIncludeWork] = useState(
@@ -1884,6 +1898,13 @@ var BrowseScreen = ({
1884
1898
  setFlash(msg);
1885
1899
  setTimeout(() => setFlash(null), 2e3);
1886
1900
  };
1901
+ const [sparkFrame, setSparkFrame] = useState(0);
1902
+ const sparkling = (mergedUrls?.length ?? 0) > 0;
1903
+ useEffect(() => {
1904
+ if (!sparkling) return;
1905
+ const id = setInterval(() => setSparkFrame((f) => f + 1), MERGED_FRAME_MS);
1906
+ return () => clearInterval(id);
1907
+ }, [sparkling]);
1887
1908
  useEffect(() => {
1888
1909
  if (refreshError) showFlash(`\u2717 ${refreshError.message}`);
1889
1910
  }, [refreshError]);
@@ -2249,6 +2270,8 @@ var BrowseScreen = ({
2249
2270
  item,
2250
2271
  active: viewStart + i === cursor,
2251
2272
  login,
2273
+ merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2274
+ sparkFrame,
2252
2275
  gap: viewStart + i > 0 && (item.kind === "repo-header" || item.kind === "subgroup-header" || // A header is always followed by a blank line before its
2253
2276
  // first child — in Other PRs that's "free" because the
2254
2277
  // child is itself a repo-header (gap above). A jira row
@@ -2415,6 +2438,9 @@ var App = ({
2415
2438
  clearInterval(id);
2416
2439
  };
2417
2440
  }, [hasCiStatus, ciFetcher, ciPollMs]);
2441
+ const [mergedUrls, setMergedUrls] = useState([]);
2442
+ const mergeTimers = useRef([]);
2443
+ useEffect(() => () => mergeTimers.current.forEach(clearTimeout), []);
2418
2444
  if (state.phase === "loading" || state.phase === "empty" || state.phase === "failed")
2419
2445
  return /* @__PURE__ */ jsxs(
2420
2446
  Box,
@@ -2460,6 +2486,20 @@ var App = ({
2460
2486
  sections: withoutItem(state.sections, target),
2461
2487
  login: state.login
2462
2488
  });
2489
+ const markMerged = (target) => {
2490
+ toBrowse();
2491
+ setMergedUrls(
2492
+ (prev) => prev.includes(target.url) ? prev : [...prev, target.url]
2493
+ );
2494
+ mergeTimers.current.push(
2495
+ setTimeout(() => {
2496
+ setMergedUrls((prev) => prev.filter((u) => u !== target.url));
2497
+ setState(
2498
+ (s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
2499
+ );
2500
+ }, MERGED_HOLD_MS)
2501
+ );
2502
+ };
2463
2503
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2464
2504
  /* @__PURE__ */ jsx(
2465
2505
  BrowseScreen,
@@ -2480,6 +2520,7 @@ var App = ({
2480
2520
  isWorkRepo,
2481
2521
  initialIncludeWork,
2482
2522
  hidden: overlay !== null,
2523
+ mergedUrls,
2483
2524
  ciStatusState: hasCiStatus ? ciStatusState : void 0,
2484
2525
  ciJob,
2485
2526
  tabHelp,
@@ -2511,7 +2552,8 @@ var App = ({
2511
2552
  login: state.login,
2512
2553
  onBack: toBrowse,
2513
2554
  onRefresh: applyOrRefresh,
2514
- onRemove: removeAndReturn
2555
+ onRemove: removeAndReturn,
2556
+ onMerged: markMerged
2515
2557
  }),
2516
2558
  overlay?.kind === "ext" && extensions?.find((e) => e.id === overlay.extId)?.body(toBrowse, overlay.target)
2517
2559
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.10.0",
3
+ "version": "0.11.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",