@d3lm/pr-stats 0.2.7 → 0.2.8

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/README.md CHANGED
@@ -28,12 +28,12 @@ pr-stats
28
28
  Without flags, it looks at PRs from the last 90 days across all repositories you can access. The tabs hold these views.
29
29
 
30
30
  - The queue tab, which it opens on, shows two lists. The open PRs awaiting your review come first with how long each has been waiting, and below them sit the open PRs you already reviewed or commented on with how long ago that was, so a PR stays visible until it merges or closes. A fresh review request moves a PR from the reviewing list back into the awaiting one.
31
- - The Your PRs tab has two sub-tabs, which the `t` key switches. The first lists your own authored PRs that are still open with their age and size. The second reports how your authored PRs got created, merged, and closed, telling a merge apart from a close without one. It charts time-to-merge percentiles, a histogram and trend, a merge-time heatmap, and a scatter of merge time against PR size. It also plots a merge-rate trend over the concluded PRs, cumulative created and merged lines whose gap shows the backlog, weekly created and merged volumes, an outcome gauge, and the most recently merged and closed PRs.
31
+ - The Your PRs tab has two sub-tabs, which the `t` key switches. The first lists your own authored PRs that are still open with their age and size. The second reports how your authored PRs got created, merged, and closed, telling a merge apart from a close without one. It charts time-to-merge percentiles, a histogram and trend, a merge-time heatmap, and a scatter of merge time against PR size. It also plots a merge-rate trend over the concluded PRs, cumulative created and merged lines whose gap shows the backlog, weekly created and merged volumes, an outcome gauge, and the most recently merged and closed PRs. A reviewer leaderboard ranks who reviews your PRs by distinct PRs reviewed, and a review-coverage gauge counts the merged PRs that never received a review. Your own replies to review threads never count as a review for either of them.
32
32
  - The time-to-review report pairs its histogram, trend, heatmap, and weekly volume with the completed review cycles per PR and a verdict gauge splitting approvals from change requests. It also shows the age of the requests still waiting on you, how old PRs already were when the request reached you, and an off-hours gauge that splits weekdays into work hours and after hours once `--work-hours` is set. On the aggregate view it additionally compares median review times by repo.
33
33
  - The PR size report carries the same histogram, trend, heatmap, and weekly volume for PR sizes and adds a net-lines trend that sums additions minus deletions per week.
34
34
  - The comments report holds a histogram of comments per PR, a scatter of comments against PR size, and the most commented PRs.
35
35
 
36
- When the data spans multiple repos, every tab opens on a repo picker that drills into one repo or the aggregate across all of them, and on the two queue lists the `g` key groups the aggregate list by repo.
36
+ When the data spans multiple repos, every tab opens on a repo picker that drills into one repo or the aggregate across all of them, and on the two queue lists the `g` key groups the aggregate list by repo. Comparison cards like the reviewer leaderboard cap themselves at eight rows and fold the rest into an overflow line, and the `x` key lifts the cap on the open stats tab and restores it. The footer names the key whenever the tab has a capped card.
37
37
 
38
38
  Node gates the FFI that OpenTUI renders through behind the `--experimental-ffi` flag, and the launcher re-executes itself with that flag when it is missing, so a plain `pr-stats` works without extra flags on both runtimes.
39
39
 
package/dist/tui-app.mjs CHANGED
@@ -146,7 +146,7 @@ import { join as join2 } from "node:path";
146
146
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
147
147
  import { homedir } from "node:os";
148
148
  import { dirname, join } from "node:path";
149
- var VERSION = 2;
149
+ var VERSION = 3;
150
150
  var enabled = false;
151
151
  function configureCache(on) {
152
152
  enabled = on;
@@ -621,10 +621,12 @@ function hintsFor(modal, editing, tab, authoredTab, views, copyLinks2) {
621
621
  if (scope?.view === "list") {
622
622
  return `\u2191/\u2193 select \xB7 enter open \xB7 ${toggle}\u2190/\u2192 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
623
623
  }
624
+ const view = views === null ? null : tab === 1 ? views.merged : tab === 2 ? views.review : tab === 3 ? views.size : views.comments;
625
+ const expand = view?.expandable ? view.expanded ? "x collapse \xB7 " : "x expand \xB7 " : "";
624
626
  if (scope !== null && repos.length > 0) {
625
- return `${toggle}esc back \xB7 j/k scroll \xB7 1-5 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
627
+ return `${toggle}${expand}esc back \xB7 j/k scroll \xB7 1-5 tabs \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
626
628
  }
627
- return `${toggle}1-5 tabs \xB7 j/k scroll \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
629
+ return `${toggle}${expand}1-5 tabs \xB7 j/k scroll \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
628
630
  }
629
631
 
630
632
  // src/tui/components/Spinner.tsx
@@ -883,6 +885,37 @@ function computeMergeStats(sizes) {
883
885
  closed.sort((a, b) => b.closedAt.getTime() - a.closedAt.getTime());
884
886
  return { merged, closed, open, allHours: merged.map((result) => result.hours) };
885
887
  }
888
+ function computeReviewerStats(sizes, author) {
889
+ const byLogin = /* @__PURE__ */ new Map();
890
+ let mergedReviewed = 0;
891
+ let mergedUnreviewed = 0;
892
+ for (const entry of sizes) {
893
+ const others = entry.reviewers.filter((login) => login !== author);
894
+ for (const login of others) {
895
+ const counts = byLogin.get(login) ?? { prs: 0, reviews: 0 };
896
+ counts.reviews += 1;
897
+ byLogin.set(login, counts);
898
+ }
899
+ const distinct = new Set(others);
900
+ for (const login of distinct) {
901
+ const counts = byLogin.get(login);
902
+ if (counts !== void 0) {
903
+ counts.prs += 1;
904
+ }
905
+ }
906
+ if (entry.mergedAt !== null) {
907
+ if (others.length > 0) {
908
+ mergedReviewed += 1;
909
+ } else {
910
+ mergedUnreviewed += 1;
911
+ }
912
+ }
913
+ }
914
+ const leaderboard = [...byLogin.entries()].map(([login, counts]) => {
915
+ return { login, ...counts };
916
+ }).toSorted((a, b) => b.prs - a.prs || b.reviews - a.reviews || a.login.localeCompare(b.login));
917
+ return { leaderboard, mergedReviewed, mergedUnreviewed };
918
+ }
886
919
  function computeCommentStats(sizes) {
887
920
  const metrics = [
888
921
  { label: "discussion comments", values: sizes.map((size) => size.comments.discussion) },
@@ -1086,9 +1119,24 @@ function useScrollbarSettle(scrollRef, mounted = true) {
1086
1119
  renderer2.off(CliRenderEvents.FRAME, release);
1087
1120
  scrollRef.current?.verticalScrollBar.resetVisibilityControl();
1088
1121
  };
1122
+ const resyncThumb = () => {
1123
+ const bar = scrollRef.current?.verticalScrollBar;
1124
+ if (!bar) {
1125
+ return;
1126
+ }
1127
+ const height = bar.viewportSize;
1128
+ const wanted = Math.max(1, height);
1129
+ if (bar.scrollSize < wanted || bar.slider.viewPortSize === wanted) {
1130
+ return;
1131
+ }
1132
+ bar.viewportSize = 0;
1133
+ bar.viewportSize = height;
1134
+ };
1089
1135
  renderer2.on(CliRenderEvents.FRAME, release);
1136
+ renderer2.on(CliRenderEvents.FRAME, resyncThumb);
1090
1137
  return () => {
1091
1138
  renderer2.off(CliRenderEvents.FRAME, release);
1139
+ renderer2.off(CliRenderEvents.FRAME, resyncThumb);
1092
1140
  };
1093
1141
  }, [scrollRef, renderer2, mounted]);
1094
1142
  }
@@ -1428,7 +1476,8 @@ var initialBrowseState = {
1428
1476
  },
1429
1477
  repoCursors: { pending: 0, open: 0, review: 0, size: 0, comment: 0, merged: 0 },
1430
1478
  rowCursors: { pending: 0, open: 0 },
1431
- grouped: { pending: false, open: false }
1479
+ grouped: { pending: false, open: false },
1480
+ expanded: { review: false, size: false, comment: false, merged: false }
1432
1481
  };
1433
1482
  function dropVanishedRepo(scope, repos) {
1434
1483
  if (scope.view === "detail" && scope.repo !== null && !repos.some((option) => option.repo === scope.repo)) {
@@ -1477,6 +1526,9 @@ function browseReducer(state, action) {
1477
1526
  case "groupingToggled": {
1478
1527
  return { ...state, grouped: { ...state.grouped, [action.tab]: !state.grouped[action.tab] } };
1479
1528
  }
1529
+ case "expandToggled": {
1530
+ return { ...state, expanded: { ...state.expanded, [action.tab]: !state.expanded[action.tab] } };
1531
+ }
1480
1532
  case "dataLoaded": {
1481
1533
  return {
1482
1534
  ...state,
@@ -3402,6 +3454,7 @@ async function fetchPrSizes(prs) {
3402
3454
  }
3403
3455
  reviews(first: 100) {
3404
3456
  nodes {
3457
+ author { login }
3405
3458
  comments {
3406
3459
  totalCount
3407
3460
  }
@@ -3596,6 +3649,7 @@ async function fetchSizeRaw(prs, onProgress, options = {}) {
3596
3649
  if (details) {
3597
3650
  const discussion = details.comments.totalCount;
3598
3651
  const review = details.reviews.nodes.reduce((sum, node) => sum + (node?.comments.totalCount ?? 0), 0);
3652
+ const reviewers = details.reviews.nodes.flatMap((node) => node?.author == null ? [] : [node.author.login]);
3599
3653
  sizes.push({
3600
3654
  pr,
3601
3655
  files: details.changedFiles,
@@ -3604,7 +3658,8 @@ async function fetchSizeRaw(prs, onProgress, options = {}) {
3604
3658
  total: details.additions + details.deletions,
3605
3659
  mergedAt: details.mergedAt === null ? null : new Date(details.mergedAt),
3606
3660
  closedAt: details.closedAt === null ? null : new Date(details.closedAt),
3607
- comments: { discussion, review, total: discussion + review }
3661
+ comments: { discussion, review, total: discussion + review },
3662
+ reviewers
3608
3663
  });
3609
3664
  }
3610
3665
  }
@@ -4048,8 +4103,8 @@ function hbar(fraction, width, color) {
4048
4103
  // src/tui/views/charts/bars.ts
4049
4104
  var BAR_WIDTH2 = 24;
4050
4105
  var MAX_BARS = 8;
4051
- function buildBarsCard({ title, subtitle, rows, format }) {
4052
- const shown = rows.slice(0, MAX_BARS);
4106
+ function buildBarsCard({ title, subtitle, rows, format, expanded = false }) {
4107
+ const shown = expanded ? rows : rows.slice(0, MAX_BARS);
4053
4108
  const max = Math.max(...shown.map((row) => row.value), 0);
4054
4109
  const labelWidth = Math.max(...shown.map((row) => row.label.length));
4055
4110
  const valueWidth = Math.max(...shown.map((row) => format(row.value).length));
@@ -4067,7 +4122,7 @@ function buildBarsCard({ title, subtitle, rows, format }) {
4067
4122
  return line;
4068
4123
  });
4069
4124
  if (rows.length > shown.length) {
4070
- lines.push([{ text: `+ ${rows.length - shown.length} more`, fg: theme.dim }]);
4125
+ lines.push([{ text: `+ ${rows.length - shown.length} more \xB7 x expands`, fg: theme.dim }]);
4071
4126
  }
4072
4127
  return { title, subtitle, lines };
4073
4128
  }
@@ -4736,7 +4791,7 @@ function buildVerdictCard(reviewed) {
4736
4791
  })
4737
4792
  });
4738
4793
  }
4739
- function buildReviewView(raw, targetHours, targetLabel, repo = null, width = 100) {
4794
+ function buildReviewView(raw, targetHours, targetLabel, repo = null, width = 100, expanded = false) {
4740
4795
  const results = repo === null ? raw.reviewResults : raw.reviewResults.filter((result) => result.pr.repo === repo);
4741
4796
  const stats = computeReviewStats(results, { targetHours, now: raw.fetchedAt });
4742
4797
  const strip = [
@@ -4751,7 +4806,9 @@ function buildReviewView(raw, targetHours, targetLabel, repo = null, width = 100
4751
4806
  distributionTitle: "Review time distribution",
4752
4807
  noCharts: "No completed reviews to chart.",
4753
4808
  cards: [],
4754
- distribution: null
4809
+ distribution: null,
4810
+ expandable: false,
4811
+ expanded
4755
4812
  };
4756
4813
  if (results.length === 0) {
4757
4814
  return { empty: "No reviewed or review-requested PRs found.", ...base, lists: [] };
@@ -4814,7 +4871,8 @@ function buildReviewView(raw, targetHours, targetLabel, repo = null, width = 100
4814
4871
  detail: `n=${hours.length}`
4815
4872
  };
4816
4873
  }).toSorted((a, b) => b.value - a.value),
4817
- format: formatDuration
4874
+ format: formatDuration,
4875
+ expanded
4818
4876
  }) : null;
4819
4877
  const cards = [
4820
4878
  buildHistogramCard({
@@ -4870,7 +4928,15 @@ function buildReviewView(raw, targetHours, targetLabel, repo = null, width = 100
4870
4928
  ticks: DURATION_TICKS,
4871
4929
  flat: (count2, value2) => `all ${count2} ${count2 === 1 ? "review" : "reviews"} took ${value2}`
4872
4930
  });
4873
- return { empty: null, ...base, headline, cards, distribution, lists };
4931
+ return {
4932
+ empty: null,
4933
+ ...base,
4934
+ headline,
4935
+ cards,
4936
+ distribution,
4937
+ lists,
4938
+ expandable: byRepoCard !== null && stats.byRepo.length > MAX_BARS
4939
+ };
4874
4940
  }
4875
4941
  function buildSizeView(raw, sizeTarget, repo = null, width = 100) {
4876
4942
  const base = {
@@ -4880,7 +4946,9 @@ function buildSizeView(raw, sizeTarget, repo = null, width = 100) {
4880
4946
  noCharts: "No authored PRs to chart.",
4881
4947
  cards: [],
4882
4948
  distribution: null,
4883
- lists: []
4949
+ lists: [],
4950
+ expandable: false,
4951
+ expanded: false
4884
4952
  };
4885
4953
  if (raw.authoredTotal === 0) {
4886
4954
  return { empty: "No authored PRs found.", ...base };
@@ -4989,7 +5057,9 @@ function buildCommentView(raw, repo = null, width = 100) {
4989
5057
  noCharts: "No authored PRs to chart.",
4990
5058
  cards: [],
4991
5059
  distribution: null,
4992
- lists: []
5060
+ lists: [],
5061
+ expandable: false,
5062
+ expanded: false
4993
5063
  };
4994
5064
  if (raw.authoredTotal === 0) {
4995
5065
  return { empty: "No authored PRs found.", ...base };
@@ -5075,7 +5145,7 @@ function buildCommentView(raw, repo = null, width = 100) {
5075
5145
  }
5076
5146
  return { empty: null, ...base, strip, headline, cards, distribution, lists };
5077
5147
  }
5078
- function buildMergedView(raw, repo = null, width = 100) {
5148
+ function buildMergedView(raw, repo = null, width = 100, expanded = false) {
5079
5149
  const base = {
5080
5150
  strip: [],
5081
5151
  headline: null,
@@ -5083,7 +5153,9 @@ function buildMergedView(raw, repo = null, width = 100) {
5083
5153
  noCharts: "No merged PRs to chart.",
5084
5154
  cards: [],
5085
5155
  distribution: null,
5086
- lists: []
5156
+ lists: [],
5157
+ expandable: false,
5158
+ expanded
5087
5159
  };
5088
5160
  if (raw.authoredTotal === 0) {
5089
5161
  return { empty: "No authored PRs found.", ...base };
@@ -5093,6 +5165,7 @@ function buildMergedView(raw, repo = null, width = 100) {
5093
5165
  return { empty: "No accessible authored PRs to analyze.", ...base };
5094
5166
  }
5095
5167
  const stats = computeMergeStats(sizes);
5168
+ const reviewers = computeReviewerStats(sizes, raw.user);
5096
5169
  const strip = [
5097
5170
  countCell(sizes.length, "PRs created"),
5098
5171
  countCell(stats.merged.length, "merged"),
@@ -5123,8 +5196,22 @@ function buildMergedView(raw, repo = null, width = 100) {
5123
5196
  )
5124
5197
  });
5125
5198
  }
5199
+ const reviewerCard = reviewers.leaderboard.length === 0 ? null : buildBarsCard({
5200
+ title: "Who reviews your PRs",
5201
+ subtitle: "distinct PRs reviewed per person",
5202
+ rows: reviewers.leaderboard.map((row) => {
5203
+ return {
5204
+ label: row.login,
5205
+ value: row.prs,
5206
+ detail: row.reviews === 1 ? "1 review" : `${row.reviews} reviews`
5207
+ };
5208
+ }),
5209
+ format: count,
5210
+ expanded
5211
+ });
5212
+ const expandable = reviewers.leaderboard.length > MAX_BARS;
5126
5213
  if (stats.merged.length === 0) {
5127
- return { empty: null, ...base, strip, lists };
5214
+ return { empty: null, ...base, strip, cards: reviewerCard === null ? [] : [reviewerCard], lists, expandable };
5128
5215
  }
5129
5216
  const sorted = [...stats.allHours].toSorted((a, b) => a - b);
5130
5217
  const headline = [
@@ -5202,7 +5289,16 @@ function buildMergedView(raw, repo = null, width = 100) {
5202
5289
  { label: "closed unmerged", count: stats.closed.length, color: theme.warn },
5203
5290
  ...stats.open.length > 0 ? [{ label: "still open", count: stats.open.length, color: theme.chartDim }] : []
5204
5291
  ]
5205
- })
5292
+ }),
5293
+ buildGaugeCard({
5294
+ title: "Review coverage",
5295
+ subtitle: "merged PRs that received a review",
5296
+ rows: [
5297
+ { label: "reviewed", count: reviewers.mergedReviewed, color: theme.accent },
5298
+ { label: "merged unreviewed", count: reviewers.mergedUnreviewed, color: theme.warn }
5299
+ ]
5300
+ }),
5301
+ ...reviewerCard === null ? [] : [reviewerCard]
5206
5302
  ];
5207
5303
  const distribution = buildDistribution({
5208
5304
  values: stats.allHours,
@@ -5211,7 +5307,7 @@ function buildMergedView(raw, repo = null, width = 100) {
5211
5307
  ticks: DURATION_TICKS,
5212
5308
  flat: (n, value2) => `all ${n} merged ${n === 1 ? "PR" : "PRs"} took ${value2}`
5213
5309
  });
5214
- return { empty: null, ...base, strip, headline, cards, distribution, lists };
5310
+ return { empty: null, ...base, strip, headline, cards, distribution, lists, expandable };
5215
5311
  }
5216
5312
  function count(value2) {
5217
5313
  return formatCount(Math.round(value2));
@@ -5227,7 +5323,7 @@ function resolveScope(scope, repos) {
5227
5323
  }
5228
5324
  return dropVanishedRepo(scope, repos);
5229
5325
  }
5230
- function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
5326
+ function useViewModel(raw, options, width, scopes, grouping, expanded, themeEpoch) {
5231
5327
  return useMemo(() => {
5232
5328
  void themeEpoch;
5233
5329
  configureTimeMode({
@@ -5253,7 +5349,7 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
5253
5349
  const reviewScope = resolveScope(scopes.review, reviewRepos);
5254
5350
  const sizeScope = resolveScope(scopes.size, sizeRepos);
5255
5351
  const commentScope = resolveScope(scopes.comment, commentRepos);
5256
- const review = reviewScope.view === "detail" ? buildReviewView(raw, targetHours, targetLabelOf(options.target), reviewScope.repo, width) : null;
5352
+ const review = reviewScope.view === "detail" ? buildReviewView(raw, targetHours, targetLabelOf(options.target), reviewScope.repo, width, expanded.review) : null;
5257
5353
  return {
5258
5354
  pendingRepos,
5259
5355
  openRepos,
@@ -5269,7 +5365,7 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
5269
5365
  commentScope,
5270
5366
  pending: pendingScope.view === "detail" ? buildPendingReviewView(raw, pendingScope.repo, grouping.pending) : null,
5271
5367
  open: openScope.view === "detail" ? buildOpenAuthoredView(raw, openScope.repo, grouping.open) : null,
5272
- merged: mergedScope.view === "detail" ? buildMergedView(raw, mergedScope.repo, width) : null,
5368
+ merged: mergedScope.view === "detail" ? buildMergedView(raw, mergedScope.repo, width, expanded.merged) : null,
5273
5369
  review,
5274
5370
  size: sizeScope.view === "detail" ? buildSizeView(raw, sizeTarget, sizeScope.repo, width) : null,
5275
5371
  comments: commentScope.view === "detail" ? buildCommentView(raw, commentScope.repo, width) : null
@@ -5290,6 +5386,8 @@ function useViewModel(raw, options, width, scopes, grouping, themeEpoch) {
5290
5386
  scopes.comment,
5291
5387
  grouping.pending,
5292
5388
  grouping.open,
5389
+ expanded.review,
5390
+ expanded.merged,
5293
5391
  themeEpoch
5294
5392
  ]);
5295
5393
  }
@@ -5519,31 +5617,35 @@ function statsTabOf(context) {
5519
5617
  return {
5520
5618
  key: "merged",
5521
5619
  repos: views?.mergedRepos ?? [],
5522
- scope: views?.mergedScope ?? null
5620
+ scope: views?.mergedScope ?? null,
5621
+ view: views?.merged ?? null
5523
5622
  };
5524
5623
  }
5525
5624
  if (context.browse.tab === 2) {
5526
5625
  return {
5527
5626
  key: "review",
5528
5627
  repos: views?.reviewRepos ?? [],
5529
- scope: views?.reviewScope ?? null
5628
+ scope: views?.reviewScope ?? null,
5629
+ view: views?.review ?? null
5530
5630
  };
5531
5631
  }
5532
5632
  if (context.browse.tab === 3) {
5533
5633
  return {
5534
5634
  key: "size",
5535
5635
  repos: views?.sizeRepos ?? [],
5536
- scope: views?.sizeScope ?? null
5636
+ scope: views?.sizeScope ?? null,
5637
+ view: views?.size ?? null
5537
5638
  };
5538
5639
  }
5539
5640
  return {
5540
5641
  key: "comment",
5541
5642
  repos: views?.commentRepos ?? [],
5542
- scope: views?.commentScope ?? null
5643
+ scope: views?.commentScope ?? null,
5644
+ view: views?.comments ?? null
5543
5645
  };
5544
5646
  }
5545
5647
  function handleStatsKey(key, context) {
5546
- const { key: tab, repos, scope } = statsTabOf(context);
5648
+ const { key: tab, repos, scope, view } = statsTabOf(context);
5547
5649
  if (scope !== null && scope.view === "list") {
5548
5650
  switch (key.name) {
5549
5651
  case "up":
@@ -5564,6 +5666,8 @@ function handleStatsKey(key, context) {
5564
5666
  }
5565
5667
  } else if ((key.name === "escape" || key.name === "backspace") && repos.length > 0) {
5566
5668
  context.dispatchBrowse({ type: "pickerReturned", tab });
5669
+ } else if (key.name === "x" && view?.expandable === true) {
5670
+ context.dispatchBrowse({ type: "expandToggled", tab });
5567
5671
  } else if (key.name === "j") {
5568
5672
  context.scrollBy(context.browse.tab, 2);
5569
5673
  } else if (key.name === "k") {
@@ -5829,7 +5933,7 @@ function App({
5829
5933
  }
5830
5934
  });
5831
5935
  });
5832
- const views = useViewModel(raw, options, width, browse.scopes, browse.grouped, themeState);
5936
+ const views = useViewModel(raw, options, width, browse.scopes, browse.grouped, browse.expanded, themeState);
5833
5937
  const showLoad = useDeferredLoading(loading, isSnapshot ? { showDelay: 0 } : void 0);
5834
5938
  const draftRef = useRef5("");
5835
5939
  const commitField = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3lm/pr-stats",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "GitHub PR stats in an interactive terminal UI, via the gh CLI or an access token",
5
5
  "type": "module",
6
6
  "author": "Dominic Elm",