@kud/gh-ink 0.32.2 → 0.33.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
@@ -295,6 +295,22 @@ declare const filterBySearch: (sections: Section[], query: string) => Section[];
295
295
  declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
296
296
  declare const withoutItem: (sections: Section[], target: GHItem) => Section[];
297
297
  declare const reposInSections: (sections: Section[]) => string[];
298
+ /**
299
+ * The next selectable row in `dir`, or `current` when there is none.
300
+ *
301
+ * A repo-header is a STOP, and a subgroup-header is not. The two look alike and
302
+ * are not: the fence names a thing you can act on — open the checkout, copy every
303
+ * URL under it — where a band label ("Your move") names an arrangement of rows
304
+ * and has nothing behind it to open. Landing on the second would be a keystroke
305
+ * spent on a row whose every key is inert.
306
+ *
307
+ * Unconditional in both directions, deliberately. Skipping the fence going down
308
+ * and landing on it going up would make ↓ then ↑ end somewhere other than where
309
+ * it started, and an arrow pair that is not its own inverse reads as the list
310
+ * drifting rather than as a shortcut. The cost is one press per repo group; the
311
+ * fence is also the "you have crossed into another repo" beat the eye takes
312
+ * anyway, so the press buys orientation as well as position.
313
+ */
298
314
  declare const moveCursor: (items: AnyItem[], current: number, dir: 1 | -1) => number;
299
315
  /**
300
316
  * What to say about the remaining budget, or nothing at all.
package/dist/index.js CHANGED
@@ -1057,7 +1057,7 @@ var reposInSections = (sections) => [
1057
1057
  ].sort();
1058
1058
  var moveCursor = (items, current2, dir) => {
1059
1059
  let next = current2 + dir;
1060
- while (next >= 0 && next < items.length && (items[next].kind === "repo-header" || items[next].kind === "subgroup-header"))
1060
+ while (next >= 0 && next < items.length && items[next].kind === "subgroup-header")
1061
1061
  next += dir;
1062
1062
  if (next < 0 || next >= items.length) return current2;
1063
1063
  return next;
@@ -1078,7 +1078,22 @@ var budgetNotice = (budget, now = Date.now()) => {
1078
1078
  };
1079
1079
  };
1080
1080
  var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
1081
+ var groupUrls = (items, i) => {
1082
+ const urls = [];
1083
+ for (let j = i + 1; j < items.length; j += 1) {
1084
+ const item = items[j];
1085
+ if (item.kind === "repo-header" || item.kind === "subgroup-header") break;
1086
+ if (item.kind === "show-more") {
1087
+ for (const child of item.hidden) if (child.url) urls.push(child.url);
1088
+ continue;
1089
+ }
1090
+ const url = item.url;
1091
+ if (url) urls.push(url);
1092
+ }
1093
+ return urls;
1094
+ };
1081
1095
  var treeUrls = (items, i) => {
1096
+ if (items[i]?.kind === "repo-header") return groupUrls(items, i);
1082
1097
  let root = i;
1083
1098
  while (root > 0 && isChildRow(items[root])) root -= 1;
1084
1099
  const urls = [];
@@ -1761,10 +1776,20 @@ var CiStatusLine = ({
1761
1776
  status.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + status.age }) : null
1762
1777
  ] });
1763
1778
  };
1764
- var RepoHeaderRow = ({ repo, gap }) => {
1779
+ var RepoHeaderRow = ({
1780
+ repo,
1781
+ gap,
1782
+ active
1783
+ }) => {
1765
1784
  const label = `\u2500\u2500 ${repo} `;
1766
1785
  const fill = Math.max(4, 46 - label.length);
1767
- return /* @__PURE__ */ jsx(Box, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label + "\u2500".repeat(fill) }) });
1786
+ const rule = "\u2500".repeat(fill);
1787
+ return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1788
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1789
+ /* @__PURE__ */ jsx(Text2, { dimColor: !active, children: "\u2500\u2500 " }),
1790
+ /* @__PURE__ */ jsx(Text2, { dimColor: !active, bold: active, children: repo }),
1791
+ /* @__PURE__ */ jsx(Text2, { dimColor: !active, children: " " + rule })
1792
+ ] });
1768
1793
  };
1769
1794
  var MERGED_HOLD_MS = 5e3;
1770
1795
  var MERGED_FRAME_MS = 150;
@@ -1802,7 +1827,7 @@ var ItemRow = ({
1802
1827
  }) => {
1803
1828
  const transient = leaving ? "out" : refreshMark;
1804
1829
  if (item.kind === "repo-header")
1805
- return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false });
1830
+ return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false, active });
1806
1831
  if (item.kind === "subgroup-header")
1807
1832
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1808
1833
  /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: true, children: " \xBB " }),
@@ -2016,13 +2041,13 @@ var HelpModal = ({
2016
2041
  const keys = [
2017
2042
  ["\u2191 \u2193", "navigate"],
2018
2043
  ["\u2190 \u2192 \xB7 tab", "switch tab"],
2019
- ["\u21B5 \xB7 d", "open / drill in"],
2044
+ ["\u21B5 \xB7 d", "open / drill in \xB7 repo: checkout"],
2020
2045
  ["m", "actions \xB7 close"],
2021
2046
  ["e", "explain this row"],
2022
2047
  ["o", "open in browser"],
2023
- ["O", "open ticket + its PRs"],
2048
+ ["O", "open tree \xB7 whole repo group"],
2024
2049
  ["c", "copy URL"],
2025
- ["C", "copy ticket + its PRs"],
2050
+ ["C", "copy tree \xB7 whole repo group"],
2026
2051
  ["b", "copy branch"],
2027
2052
  ["s", "switch to branch here"],
2028
2053
  ["j", "open repo in new tab"],
@@ -2462,7 +2487,11 @@ var BrowseScreen = ({
2462
2487
  const ext = extensionFor(input, extensions);
2463
2488
  if (ext) {
2464
2489
  onOpenExt?.(ext.id, {
2465
- item: activeItem ?? void 0,
2490
+ // ExtensionTarget.item has always been documented as absent on a header
2491
+ // row, and until the fence became selectable that was true for free.
2492
+ // Now it has to be said: every extension body narrows on `kind` for a
2493
+ // pr / issue / task and none of them expect a repo-header.
2494
+ item: activeItem && !isHeader(activeItem) ? activeItem : void 0,
2466
2495
  ciJob: ciStatus?.job,
2467
2496
  login,
2468
2497
  onRemove: (row) => dismissItem(row),
@@ -2470,8 +2499,42 @@ var BrowseScreen = ({
2470
2499
  });
2471
2500
  return;
2472
2501
  }
2473
- if (!activeItem || activeItem.kind === "repo-header" || activeItem.kind === "subgroup-header")
2502
+ if (activeItem?.kind === "repo-header") {
2503
+ const { repo } = activeItem;
2504
+ const repoUrl = `https://github.com/${repo}`;
2505
+ if (key.return || input === "d" || input === "j") {
2506
+ showFlash(`\u22EF Opening ${repo}\u2026`);
2507
+ void jumpToRepo(repo, "", login).then(() => showFlash(`\u2197 Opened ${repo} in new tab`)).catch(() => showFlash("\u2717 Jump failed"));
2508
+ return;
2509
+ }
2510
+ if (input === "o") {
2511
+ quietly`open ${repoUrl}`.catch(() => {
2512
+ });
2513
+ showFlash(`\u2197 Opened ${repo}`);
2514
+ return;
2515
+ }
2516
+ if (input === "c") {
2517
+ clipboard(repoUrl);
2518
+ showFlash(`\u2713 Copied URL for ${repo}`);
2519
+ return;
2520
+ }
2521
+ if (input === "C" || input === "O") {
2522
+ const urls = treeUrls(section.items, cursor);
2523
+ if (urls.length === 0) return;
2524
+ const n = `${urls.length} URL${urls.length === 1 ? "" : "s"}`;
2525
+ if (input === "C") {
2526
+ clipboard(urls.join("\n"));
2527
+ showFlash(`\u2713 Copied ${n} from ${repo}`);
2528
+ } else {
2529
+ quietly`open ${urls}`.catch(() => {
2530
+ });
2531
+ showFlash(`\u2197 Opened ${n} from ${repo}`);
2532
+ }
2533
+ return;
2534
+ }
2474
2535
  return;
2536
+ }
2537
+ if (!activeItem || activeItem.kind === "subgroup-header") return;
2475
2538
  if (key.return && activeItem.kind === "show-more") {
2476
2539
  setLocalSections(
2477
2540
  (prev) => prev.map((s) => ({
@@ -2651,7 +2714,15 @@ var BrowseScreen = ({
2651
2714
  menu.open(actions);
2652
2715
  }
2653
2716
  });
2654
- const hints = [
2717
+ const hints = activeItem?.kind === "repo-header" ? [
2718
+ ["\u2191\u2193", "nav"],
2719
+ ["\u2190\u2192", "tab"],
2720
+ ["\u21B5", "open repo"],
2721
+ ["o", "browser"],
2722
+ ["C", "copy group"],
2723
+ ["?", "help"],
2724
+ ["q", "quit"]
2725
+ ] : [
2655
2726
  ["\u2191\u2193", "nav"],
2656
2727
  ["\u2190\u2192", "tab"],
2657
2728
  ["\u21B5/d", "open"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.32.2",
3
+ "version": "0.33.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",