@kud/gh-ink 0.33.0 → 0.34.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
@@ -140,7 +140,23 @@ type GHItem = {
140
140
  * handing back a `lastActor` that no longer claims a turn.
141
141
  */
142
142
  pinned?: boolean;
143
- indent: boolean;
143
+ /**
144
+ * How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
145
+ * grandchild. Absent means 0.
146
+ *
147
+ * Always read it through `depthOf`, never off the row — `indent` is still a
148
+ * legal way to spell "depth 1", and that helper is the only place that knows
149
+ * both spellings. A site that reads `item.indent` directly prices a `depth: 2`
150
+ * row as top level, which is the bug this field exists to end rather than a
151
+ * new one to introduce.
152
+ */
153
+ depth?: number;
154
+ /**
155
+ * @deprecated Legacy spelling of `depth: 1`. Write `depth`; this is kept so
156
+ * every existing producer still typechecks, and it is read in exactly one
157
+ * place (`depthOf`). Due to be removed at the next major.
158
+ */
159
+ indent?: boolean;
144
160
  };
145
161
  type TaskRow = {
146
162
  kind: "task";
@@ -149,7 +165,10 @@ type TaskRow = {
149
165
  url: string;
150
166
  status: string;
151
167
  age: string;
152
- indent: boolean;
168
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
169
+ depth?: number;
170
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
171
+ indent?: boolean;
153
172
  instanceKey?: string;
154
173
  /**
155
174
  * Trailing annotation, rendered dim after the summary — a recurrence marker,
@@ -176,23 +195,35 @@ type RepoHeader = {
176
195
  kind: "repo-header";
177
196
  repo: string;
178
197
  age: string;
179
- indent: boolean;
198
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
199
+ depth?: number;
200
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
201
+ indent?: boolean;
180
202
  };
181
203
  type ShowMore = {
182
204
  kind: "show-more";
183
205
  hidden: GHItem[];
184
- indent: boolean;
206
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
207
+ depth?: number;
208
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
209
+ indent?: boolean;
185
210
  };
186
211
  type ShowLess = {
187
212
  kind: "show-less";
188
213
  toHide: GHItem[];
189
- indent: boolean;
214
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
215
+ depth?: number;
216
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
217
+ indent?: boolean;
190
218
  };
191
219
  type SubgroupHeader = {
192
220
  kind: "subgroup-header";
193
221
  label: string;
194
222
  age: string;
195
- indent: boolean;
223
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
224
+ depth?: number;
225
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
226
+ indent?: boolean;
196
227
  };
197
228
  type AnyItem = GHItem | TaskRow | RepoHeader | SubgroupHeader | ShowMore | ShowLess;
198
229
  type CiStatus = {
package/dist/index.js CHANGED
@@ -780,6 +780,11 @@ var tabsOfMarks = (sections, marks) => {
780
780
  return out;
781
781
  };
782
782
  var quietly = $({ quiet: true });
783
+ var depthOf = (item) => {
784
+ if (!item) return 0;
785
+ if (typeof item.depth === "number") return Math.max(0, Math.floor(item.depth));
786
+ return item.indent === true ? 1 : 0;
787
+ };
783
788
  var relativeTime = (iso) => {
784
789
  const diff = (Date.now() - new Date(iso).getTime()) / 1e3;
785
790
  if (diff < 3600) return `${Math.floor(diff / 60)}m`;
@@ -902,7 +907,7 @@ var insertRepoHeaders = (items) => {
902
907
  const result = [];
903
908
  let lastRepo = "";
904
909
  for (const item of items) {
905
- if (!item.indent && item.repo !== lastRepo) {
910
+ if (depthOf(item) === 0 && item.repo !== lastRepo) {
906
911
  lastRepo = item.repo;
907
912
  result.push({
908
913
  kind: "repo-header",
@@ -1077,7 +1082,30 @@ var budgetNotice = (budget, now = Date.now()) => {
1077
1082
  critical: false
1078
1083
  };
1079
1084
  };
1080
- var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
1085
+ var isLastAtDepth = (items, i) => {
1086
+ const depth = depthOf(items[i]);
1087
+ for (let j = i + 1; j < items.length; j += 1) {
1088
+ const d = depthOf(items[j]);
1089
+ if (d > depth) continue;
1090
+ return d < depth;
1091
+ }
1092
+ return true;
1093
+ };
1094
+ var treePrefix = (items, i) => {
1095
+ const depth = depthOf(items[i]);
1096
+ if (depth === 0) return "";
1097
+ const ancestorLast = [];
1098
+ let level = depth - 1;
1099
+ for (let j = i - 1; j >= 0 && level >= 1; j -= 1) {
1100
+ if (depthOf(items[j]) === level) {
1101
+ ancestorLast[level] = isLastAtDepth(items, j);
1102
+ level -= 1;
1103
+ }
1104
+ }
1105
+ let prefix = "";
1106
+ for (let k = 1; k < depth; k += 1) prefix += ancestorLast[k] ? " " : "\u2502 ";
1107
+ return prefix + (isLastAtDepth(items, i) ? "\u2514\u2500 " : "\u251C\u2500 ");
1108
+ };
1081
1109
  var groupUrls = (items, i) => {
1082
1110
  const urls = [];
1083
1111
  for (let j = i + 1; j < items.length; j += 1) {
@@ -1095,11 +1123,13 @@ var groupUrls = (items, i) => {
1095
1123
  var treeUrls = (items, i) => {
1096
1124
  if (items[i]?.kind === "repo-header") return groupUrls(items, i);
1097
1125
  let root = i;
1098
- while (root > 0 && isChildRow(items[root])) root -= 1;
1126
+ while (root > 0 && depthOf(items[root]) > 0 && items[root]?.kind !== "task")
1127
+ root -= 1;
1128
+ const rootDepth = depthOf(items[root]);
1099
1129
  const urls = [];
1100
1130
  for (let j = root; j < items.length; j += 1) {
1101
1131
  const item = items[j];
1102
- if (j > root && !isChildRow(item)) break;
1132
+ if (j > root && depthOf(item) <= rootDepth) break;
1103
1133
  if (item.kind === "show-more") {
1104
1134
  for (const child of item.hidden) if (child.url) urls.push(child.url);
1105
1135
  continue;
@@ -1115,7 +1145,8 @@ var gapsAbove = (items, i) => {
1115
1145
  if (item.kind === "repo-header" || item.kind === "subgroup-header")
1116
1146
  return true;
1117
1147
  if (item.kind !== "task") return false;
1118
- return isChildRow(items[i + 1]) || isChildRow(items[i - 1]);
1148
+ if (depthOf(item) > 0) return false;
1149
+ return depthOf(items[i + 1]) > 0 || depthOf(items[i - 1]) > 0;
1119
1150
  };
1120
1151
  var fitCount = (items, start, budget) => {
1121
1152
  let lines = 0;
@@ -1360,7 +1391,7 @@ var Backdrop = ({
1360
1391
  }
1361
1392
  ) : children });
1362
1393
  var topLevelCount = (s) => s.items.filter(
1363
- (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && !i.indent
1394
+ (i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && depthOf(i) === 0
1364
1395
  ).length;
1365
1396
  var drillCmd = (item) => {
1366
1397
  if (item.kind === "task")
@@ -1608,7 +1639,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1608
1639
  }
1609
1640
  });
1610
1641
  if (jiraBase && jiraKeyRe) {
1611
- const jiraKey = !item.indent ? item.title.match(jiraKeyRe)?.[0] : null;
1642
+ const jiraKey = depthOf(item) === 0 ? item.title.match(jiraKeyRe)?.[0] : null;
1612
1643
  if (jiraKey) {
1613
1644
  actions.push({
1614
1645
  label: `Open ${jiraKey} in Jira`,
@@ -1776,13 +1807,14 @@ var CiStatusLine = ({
1776
1807
  status.age ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + status.age }) : null
1777
1808
  ] });
1778
1809
  };
1810
+ var FENCE_COLS = 46;
1779
1811
  var RepoHeaderRow = ({
1780
1812
  repo,
1781
1813
  gap,
1782
1814
  active
1783
1815
  }) => {
1784
1816
  const label = `\u2500\u2500 ${repo} `;
1785
- const fill = Math.max(4, 46 - label.length);
1817
+ const fill = Math.max(4, FENCE_COLS - label.length);
1786
1818
  const rule = "\u2500".repeat(fill);
1787
1819
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1788
1820
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
@@ -1821,7 +1853,7 @@ var ItemRow = ({
1821
1853
  merged,
1822
1854
  transient: refreshMark,
1823
1855
  leaving,
1824
- lastChild,
1856
+ prefix = "",
1825
1857
  parent,
1826
1858
  sparkFrame = 0
1827
1859
  }) => {
@@ -1836,14 +1868,14 @@ var ItemRow = ({
1836
1868
  if (item.kind === "show-more")
1837
1869
  return /* @__PURE__ */ jsxs(Box, { children: [
1838
1870
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1839
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }),
1871
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1840
1872
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1841
1873
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: `+${item.hidden.length} more` })
1842
1874
  ] });
1843
1875
  if (item.kind === "show-less")
1844
1876
  return /* @__PURE__ */ jsxs(Box, { children: [
1845
1877
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1846
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "\u2514\u2500 " }),
1878
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1847
1879
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: active ? "\u21B5 " : " " }),
1848
1880
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "show less" })
1849
1881
  ] });
@@ -1853,10 +1885,11 @@ var ItemRow = ({
1853
1885
  const transitLabel2 = transient ? TRANSIT_LABEL[transient] : "";
1854
1886
  const titleMax2 = Math.max(
1855
1887
  20,
1856
- COLS - item.key.length - note.length - transitLabel2.length - 12
1888
+ COLS - item.key.length - note.length - transitLabel2.length - prefix.length - 12
1857
1889
  );
1858
1890
  return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, children: [
1859
1891
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1892
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1860
1893
  /* @__PURE__ */ jsx(Text2, { dimColor: true, children: parent ? "\u252C " : " " }),
1861
1894
  /* @__PURE__ */ jsx(Text2, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
1862
1895
  /* @__PURE__ */ jsx(Text2, { color: "#FF8700", bold: active, children: item.key + " " }),
@@ -1891,12 +1924,12 @@ var ItemRow = ({
1891
1924
  mergedLabel,
1892
1925
  transitLabel
1893
1926
  ].filter(Boolean).join(" ");
1894
- const repoLabel = item.indent ? item.repo : "";
1895
- const fixedWidth = 2 + (item.indent ? 3 : 0) + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
1927
+ const repoLabel = depthOf(item) > 0 ? item.repo : "";
1928
+ const fixedWidth = 2 + prefix.length + 2 + 2 + 7 + repoLabel.length + suffix.length + 6;
1896
1929
  const titleMax = Math.max(20, COLS - fixedWidth);
1897
1930
  return /* @__PURE__ */ jsxs(Box, { children: [
1898
1931
  /* @__PURE__ */ jsx(Text2, { color: "cyan", children: active ? "\u276F " : " " }),
1899
- item.indent ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: lastChild === false ? "\u251C\u2500 " : "\u2514\u2500 " }) : null,
1932
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: prefix }),
1900
1933
  /* @__PURE__ */ jsx(Text2, { color, bold: true, children: icon + " " }),
1901
1934
  /* @__PURE__ */ jsx(Text2, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
1902
1935
  /* @__PURE__ */ jsx(Text2, { color: "#FF8700", children: numStr }),
@@ -2545,7 +2578,12 @@ var BrowseScreen = ({
2545
2578
  {
2546
2579
  kind: "show-less",
2547
2580
  toHide: activeItem.hidden,
2548
- indent: true
2581
+ // The depth of the row it replaces, never a constant. A
2582
+ // collapsed row sitting under a story is at depth 2, and
2583
+ // pinning it to 1 makes the walk in `treeUrls` stop one
2584
+ // level early and drop every row it holds — silently, since
2585
+ // nothing on screen reports what the clipboard missed.
2586
+ depth: depthOf(activeItem)
2549
2587
  }
2550
2588
  ] : [i]
2551
2589
  )
@@ -2562,7 +2600,9 @@ var BrowseScreen = ({
2562
2600
  {
2563
2601
  kind: "show-more",
2564
2602
  hidden: activeItem.toHide,
2565
- indent: true
2603
+ // Same reasoning as the show-less above: carry the depth
2604
+ // of the row being replaced, not a constant.
2605
+ depth: depthOf(activeItem)
2566
2606
  }
2567
2607
  ] : [i]
2568
2608
  )
@@ -2814,8 +2854,8 @@ var BrowseScreen = ({
2814
2854
  merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
2815
2855
  leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
2816
2856
  transient: transientOf(transients, item),
2817
- lastChild: "indent" in item && item.indent ? !isChildRow(section.items[viewStart + i + 1]) : void 0,
2818
- parent: item.kind === "task" && isChildRow(section.items[viewStart + i + 1]),
2857
+ prefix: treePrefix(section.items, viewStart + i),
2858
+ parent: item.kind === "task" && depthOf(section.items[viewStart + i + 1]) > depthOf(item),
2819
2859
  sparkFrame,
2820
2860
  gap: i > 0 && gapsAbove(section.items, viewStart + i)
2821
2861
  },
@@ -3023,7 +3063,7 @@ var App = ({
3023
3063
  receive(fresh);
3024
3064
  }).catch((err) => {
3025
3065
  setRefreshing(false);
3026
- const message = err.message;
3066
+ const message = explainGhAction(err);
3027
3067
  if (!displayedKey.current) {
3028
3068
  setState({ phase: "failed", message });
3029
3069
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.33.0",
3
+ "version": "0.34.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",