clairo 2.2.0 → 3.0.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +551 -124
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,8 @@ dashboard tui for github PRs, jira tickets, and daily logs.
11
11
  - view open PRs with status, reviews, and checks
12
12
  - see full PR details with description, labels, and assignees
13
13
  - create new PRs from the terminal
14
+ - browse all PRs across the repo with state, search, and status filters
15
+ - checkout PRs directly from the detail view
14
16
  - jira integration
15
17
  - auto ticket detection based on branch name
16
18
  - link tickets and change status from the terminal
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import meow from "meow";
5
5
 
6
6
  // src/app.tsx
7
- import { useCallback as useCallback11, useMemo as useMemo4, useState as useState22 } from "react";
8
- import { Box as Box22, Text as Text21, useApp, useInput as useInput18 } from "ink";
7
+ import { useCallback as useCallback12, useMemo as useMemo4, useState as useState23 } from "react";
8
+ import { Box as Box23, Text as Text22, useApp, useInput as useInput19 } from "ink";
9
9
 
10
10
  // src/components/github/GitHubView.tsx
11
11
  import { useCallback as useCallback8, useEffect as useEffect9, useRef as useRef5, useState as useState12 } from "react";
@@ -194,7 +194,7 @@ function resolveCheckStatus(check) {
194
194
  const conclusion = check.conclusion ?? check.state;
195
195
  if (conclusion === "SUCCESS") return "success";
196
196
  if (conclusion === "FAILURE" || conclusion === "ERROR") return "failure";
197
- if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "skipped";
197
+ if (conclusion === "SKIPPED" || conclusion === "NEUTRAL" || conclusion === "CANCELLED") return "skipped";
198
198
  if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
199
199
  return "pending";
200
200
  return "pending";
@@ -261,7 +261,7 @@ async function listPRsForBranch(branch, repo) {
261
261
  errorType: "not_authenticated"
262
262
  };
263
263
  }
264
- const fields = "number,title,state,author,createdAt,isDraft";
264
+ const fields = "number,title,state,author,createdAt,isDraft,reviewDecision,statusCheckRollup";
265
265
  try {
266
266
  const { stdout } = await execAsync(`gh pr view --json ${fields} 2>/dev/null`);
267
267
  const pr = JSON.parse(stdout);
@@ -1146,25 +1146,45 @@ async function getBoardIssues(auth, boardId, opts) {
1146
1146
  function escapeJql(text) {
1147
1147
  return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1148
1148
  }
1149
- function appendTextSearch(jql, searchText) {
1149
+ function createSearchClause(searchText) {
1150
1150
  const escaped = escapeJql(searchText);
1151
- return `(${jql}) AND text ~ "${escaped}"`;
1151
+ if (/^[A-Z]+-\d+$/i.test(searchText)) {
1152
+ return `(key = "${escaped}" OR text ~ "${escaped}")`;
1153
+ }
1154
+ if (/^\d+$/.test(searchText)) {
1155
+ return `(key ~ "*-${escaped}" OR text ~ "${escaped}")`;
1156
+ }
1157
+ return `text ~ "${escaped}"`;
1158
+ }
1159
+ function appendTextSearch(jql, searchText) {
1160
+ return `(${jql}) AND ${createSearchClause(searchText)}`;
1161
+ }
1162
+ function createAssigneeClause(filter) {
1163
+ if (filter === "unassigned") return "assignee is EMPTY";
1164
+ return "assignee = currentUser()";
1152
1165
  }
1153
1166
  async function fetchViewIssues(auth, view, opts) {
1154
- const { searchText, ...pageOpts } = opts ?? {};
1167
+ const { searchText, assigneeFilter, ...pageOpts } = opts ?? {};
1155
1168
  switch (view.source.type) {
1156
1169
  case "jql": {
1157
- const jql = searchText ? appendTextSearch(view.source.jql, searchText) : view.source.jql;
1170
+ let jql = view.source.jql;
1171
+ if (searchText) jql = appendTextSearch(jql, searchText);
1172
+ if (assigneeFilter) jql = `(${jql}) AND ${createAssigneeClause(assigneeFilter)}`;
1158
1173
  return searchIssues(auth, jql, pageOpts);
1159
1174
  }
1160
1175
  case "filter": {
1161
1176
  const filterResult = await getFilterJql(auth, view.source.filterId);
1162
1177
  if (!filterResult.success) return filterResult;
1163
- const jql = searchText ? appendTextSearch(filterResult.data, searchText) : filterResult.data;
1178
+ let jql = filterResult.data;
1179
+ if (searchText) jql = appendTextSearch(jql, searchText);
1180
+ if (assigneeFilter) jql = `(${jql}) AND ${createAssigneeClause(assigneeFilter)}`;
1164
1181
  return searchIssues(auth, jql, pageOpts);
1165
1182
  }
1166
1183
  case "board": {
1167
- const jql = searchText ? `text ~ "${escapeJql(searchText)}"` : void 0;
1184
+ const clauses = [];
1185
+ if (searchText) clauses.push(createSearchClause(searchText));
1186
+ if (assigneeFilter) clauses.push(createAssigneeClause(assigneeFilter));
1187
+ const jql = clauses.length > 0 ? clauses.join(" AND ") : void 0;
1168
1188
  return getBoardIssues(auth, view.source.boardId, { ...pageOpts, jql });
1169
1189
  }
1170
1190
  }
@@ -1537,6 +1557,7 @@ import open from "open";
1537
1557
  import { useRef as useRef2 } from "react";
1538
1558
  import { Box as Box3, Text as Text3, useInput, useStdout } from "ink";
1539
1559
  import { ScrollView } from "ink-scroll-view";
1560
+ import Spinner from "ink-spinner";
1540
1561
 
1541
1562
  // src/components/ui/Badge.tsx
1542
1563
  import { Text } from "ink";
@@ -1681,10 +1702,9 @@ function renderInlineToString(tokens) {
1681
1702
 
1682
1703
  // src/components/github/PRDetailsBox.tsx
1683
1704
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1684
- function PRDetailsBox({ pr, loading, error, isActive }) {
1705
+ function PRDetailsBox({ pr, loading, error, isActive, title = "[3] PR Details" }) {
1685
1706
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1686
1707
  const scrollRef = useRef2(null);
1687
- const title = "[3] PR Details";
1688
1708
  const borderColor = isActive ? "yellow" : void 0;
1689
1709
  const displayTitle = pr ? `${title} - #${pr.number}` : title;
1690
1710
  const reviewDisplay = resolveReviewDisplay((pr == null ? void 0 : pr.reviewDecision) ?? null);
@@ -1724,7 +1744,10 @@ function PRDetailsBox({ pr, loading, error, isActive }) {
1724
1744
  borderTop: false,
1725
1745
  borderColor,
1726
1746
  children: /* @__PURE__ */ jsx4(ScrollView, { ref: scrollRef, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
1727
- loading && /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "Loading details..." }),
1747
+ loading && /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
1748
+ /* @__PURE__ */ jsx4(Spinner, { type: "dots" }),
1749
+ " Loading details..."
1750
+ ] }),
1728
1751
  error && /* @__PURE__ */ jsx4(Text3, { color: "red", children: error }),
1729
1752
  !loading && !error && !pr && /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "Select a PR to view details" }),
1730
1753
  !loading && !error && pr && /* @__PURE__ */ jsxs3(Fragment2, { children: [
@@ -2075,13 +2098,13 @@ function useLogs() {
2075
2098
  import { useCallback as useCallback6, useState as useState7 } from "react";
2076
2099
  function useModal() {
2077
2100
  const [modalType, setModalType] = useState7("none");
2078
- const open6 = useCallback6((type) => setModalType(type), []);
2101
+ const open7 = useCallback6((type) => setModalType(type), []);
2079
2102
  const close = useCallback6(() => setModalType("none"), []);
2080
2103
  const isOpen = modalType !== "none";
2081
2104
  return {
2082
2105
  type: modalType,
2083
2106
  isOpen,
2084
- open: open6,
2107
+ open: open7,
2085
2108
  close
2086
2109
  };
2087
2110
  }
@@ -3047,18 +3070,18 @@ var proto = Object.defineProperties(() => {
3047
3070
  }
3048
3071
  }
3049
3072
  });
3050
- var createStyler = (open6, close, parent) => {
3073
+ var createStyler = (open7, close, parent) => {
3051
3074
  let openAll;
3052
3075
  let closeAll;
3053
3076
  if (parent === void 0) {
3054
- openAll = open6;
3077
+ openAll = open7;
3055
3078
  closeAll = close;
3056
3079
  } else {
3057
- openAll = parent.openAll + open6;
3080
+ openAll = parent.openAll + open7;
3058
3081
  closeAll = close + parent.closeAll;
3059
3082
  }
3060
3083
  return {
3061
- open: open6,
3084
+ open: open7,
3062
3085
  close,
3063
3086
  openAll,
3064
3087
  closeAll,
@@ -3767,7 +3790,7 @@ import { useCallback as useCallback9, useEffect as useEffect11, useMemo as useMe
3767
3790
  import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
3768
3791
  import { Box as Box10, Text as Text11, useInput as useInput8 } from "ink";
3769
3792
  import { ScrollView as ScrollView6 } from "ink-scroll-view";
3770
- import Spinner2 from "ink-spinner";
3793
+ import Spinner3 from "ink-spinner";
3771
3794
 
3772
3795
  // src/components/jira-browser/JiraIssueDetailView.tsx
3773
3796
  import open3 from "open";
@@ -3775,7 +3798,7 @@ import { useEffect as useEffect10, useRef as useRef6, useState as useState14 } f
3775
3798
  import { Box as Box9, Text as Text10, useInput as useInput7 } from "ink";
3776
3799
  import { ScrollView as ScrollView5 } from "ink-scroll-view";
3777
3800
  import SelectInput from "ink-select-input";
3778
- import Spinner from "ink-spinner";
3801
+ import Spinner2 from "ink-spinner";
3779
3802
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3780
3803
  function JiraIssueDetailView({
3781
3804
  issueKey,
@@ -3783,6 +3806,7 @@ function JiraIssueDetailView({
3783
3806
  auth,
3784
3807
  myAccountId,
3785
3808
  myDisplayName,
3809
+ onFetchCurrentUser,
3786
3810
  isActive,
3787
3811
  onClose,
3788
3812
  onIssueUpdated,
@@ -3844,16 +3868,27 @@ function JiraIssueDetailView({
3844
3868
  setActionLoading(null);
3845
3869
  };
3846
3870
  const handleAssignToMe = async () => {
3847
- if (!myAccountId || !myDisplayName) return;
3848
3871
  setActionLoading("Assigning...");
3849
3872
  setActionError(null);
3850
- const result = await assignIssue(auth, issueKey, myAccountId);
3873
+ let accountId = myAccountId;
3874
+ let displayName = myDisplayName;
3875
+ if (!accountId || !displayName) {
3876
+ const user = await onFetchCurrentUser();
3877
+ if (!user) {
3878
+ setActionError("Failed to get current user");
3879
+ setActionLoading(null);
3880
+ return;
3881
+ }
3882
+ accountId = user.accountId;
3883
+ displayName = user.displayName;
3884
+ }
3885
+ const result = await assignIssue(auth, issueKey, accountId);
3851
3886
  if (result.success) {
3852
- const assignee = { accountId: myAccountId, displayName: myDisplayName };
3887
+ const assignee = { accountId, displayName };
3853
3888
  setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee } } : prev);
3854
3889
  onIssueUpdated(issueKey, { assignee });
3855
- duckEvents.emit("jira:assigned", { ticketKey: issueKey, assignee: myDisplayName });
3856
- logJiraAssigneeChanged(issueKey, issueSummary, "assigned", myDisplayName);
3890
+ duckEvents.emit("jira:assigned", { ticketKey: issueKey, assignee: displayName });
3891
+ logJiraAssigneeChanged(issueKey, issueSummary, "assigned", displayName);
3857
3892
  onLogUpdated == null ? void 0 : onLogUpdated();
3858
3893
  } else {
3859
3894
  setActionError(result.error);
@@ -3906,7 +3941,7 @@ function JiraIssueDetailView({
3906
3941
  if (input === "s" && !actionLoading) {
3907
3942
  openTransitionPicker();
3908
3943
  }
3909
- if (input === "a" && !actionLoading && myAccountId) {
3944
+ if (input === "a" && !actionLoading) {
3910
3945
  handleAssignToMe();
3911
3946
  }
3912
3947
  if (input === "A" && !actionLoading) {
@@ -3922,7 +3957,7 @@ function JiraIssueDetailView({
3922
3957
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", flexGrow: 1, children: [
3923
3958
  /* @__PURE__ */ jsxs9(Box9, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
3924
3959
  loading && /* @__PURE__ */ jsx11(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
3925
- /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
3960
+ /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
3926
3961
  " Loading issue details..."
3927
3962
  ] }) }),
3928
3963
  error && /* @__PURE__ */ jsx11(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { color: "red", children: error }) }),
@@ -3979,13 +4014,13 @@ function JiraIssueDetailView({
3979
4014
  ] }),
3980
4015
  /* @__PURE__ */ jsxs9(Box9, { paddingX: 1, flexDirection: "column", children: [
3981
4016
  actionLoading && /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
3982
- /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
4017
+ /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
3983
4018
  " ",
3984
4019
  actionLoading
3985
4020
  ] }),
3986
4021
  actionError && /* @__PURE__ */ jsx11(Text10, { color: "red", children: actionError }),
3987
4022
  transitionsLoading && /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
3988
- /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
4023
+ /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
3989
4024
  " Loading transitions..."
3990
4025
  ] }),
3991
4026
  !actionLoading && !transitionsLoading && mode === "normal" && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "Esc close \xB7 j/k scroll \xB7 s status \xB7 a assign \xB7 A unassign \xB7 o open \xB7 y copy" })
@@ -4054,6 +4089,7 @@ function JiraSavedViewBrowserBox({
4054
4089
  auth,
4055
4090
  myAccountId,
4056
4091
  myDisplayName,
4092
+ onFetchCurrentUser,
4057
4093
  isActive,
4058
4094
  onInputModeChange,
4059
4095
  onLogUpdated
@@ -4074,22 +4110,10 @@ function JiraSavedViewBrowserBox({
4074
4110
  const title = "[6] Issues";
4075
4111
  const borderColor = isActive ? "yellow" : void 0;
4076
4112
  const displayTitle = view ? `${title} - ${view.name}` : title;
4077
- const filteredIssues = useMemo2(() => {
4078
- if (assigneeFilter === "unassigned") {
4079
- return issues.filter((issue) => !issue.fields.assignee);
4080
- }
4081
- if (assigneeFilter === "me" && myAccountId) {
4082
- return issues.filter((issue) => {
4083
- var _a;
4084
- return ((_a = issue.fields.assignee) == null ? void 0 : _a.accountId) === myAccountId;
4085
- });
4086
- }
4087
- return issues;
4088
- }, [issues, assigneeFilter, myAccountId]);
4089
4113
  const rows = useMemo2(() => {
4090
- const groups = groupBySprint(filteredIssues);
4114
+ const groups = groupBySprint(issues);
4091
4115
  return buildRows(groups);
4092
- }, [filteredIssues]);
4116
+ }, [issues]);
4093
4117
  const navigableIndices = useMemo2(
4094
4118
  () => rows.map((r, i) => r.type === "issue" ? i : -1).filter((i) => i >= 0),
4095
4119
  [rows]
@@ -4104,14 +4128,19 @@ function JiraSavedViewBrowserBox({
4104
4128
  }, [rows, navigableIndices, highlightedIndex]);
4105
4129
  const hasMore = issues.length < total;
4106
4130
  const doFetch = useCallback9(
4107
- async (search, startAt = 0, append = false) => {
4131
+ async (search, filter, startAt = 0, append = false) => {
4108
4132
  if (!view || !auth) return;
4109
4133
  setLoading(true);
4110
4134
  setError(null);
4135
+ if (!append) {
4136
+ setIssues([]);
4137
+ setTotal(0);
4138
+ }
4111
4139
  const result = await fetchViewIssues(auth, view, {
4112
4140
  startAt,
4113
4141
  maxResults: 50,
4114
- searchText: search || void 0
4142
+ searchText: search || void 0,
4143
+ assigneeFilter: filter === "all" ? void 0 : filter
4115
4144
  });
4116
4145
  if (result.success) {
4117
4146
  setIssues((prev) => append ? [...prev, ...result.data.issues] : result.data.issues);
@@ -4132,7 +4161,8 @@ function JiraSavedViewBrowserBox({
4132
4161
  if (view && auth) {
4133
4162
  setSearchText("");
4134
4163
  setInputText("");
4135
- doFetch("");
4164
+ setAssigneeFilter("all");
4165
+ doFetch("", "all");
4136
4166
  } else {
4137
4167
  setIssues([]);
4138
4168
  setTotal(0);
@@ -4176,7 +4206,7 @@ function JiraSavedViewBrowserBox({
4176
4206
  const newSearch = inputText.trim();
4177
4207
  if (newSearch !== searchText) {
4178
4208
  setSearchText(newSearch);
4179
- doFetch(newSearch);
4209
+ doFetch(newSearch, assigneeFilter);
4180
4210
  }
4181
4211
  return;
4182
4212
  }
@@ -4219,31 +4249,33 @@ function JiraSavedViewBrowserBox({
4219
4249
  return;
4220
4250
  }
4221
4251
  if (input === "u") {
4222
- setAssigneeFilter((f) => f === "unassigned" ? "all" : "unassigned");
4252
+ const newFilter = assigneeFilter === "unassigned" ? "all" : "unassigned";
4253
+ setAssigneeFilter(newFilter);
4254
+ doFetch(searchText, newFilter);
4223
4255
  setHighlightedIndex(0);
4224
4256
  return;
4225
4257
  }
4226
4258
  if (input === "m") {
4227
- setAssigneeFilter((f) => f === "me" ? "all" : "me");
4259
+ const newFilter = assigneeFilter === "me" ? "all" : "me";
4260
+ setAssigneeFilter(newFilter);
4261
+ doFetch(searchText, newFilter);
4228
4262
  setHighlightedIndex(0);
4229
4263
  return;
4230
4264
  }
4231
4265
  if (input === "x") {
4232
4266
  setAssigneeFilter("all");
4233
- if (searchText) {
4234
- setSearchText("");
4235
- setInputText("");
4236
- doFetch("");
4237
- }
4267
+ setSearchText("");
4268
+ setInputText("");
4269
+ doFetch("", "all");
4238
4270
  setHighlightedIndex(0);
4239
4271
  return;
4240
4272
  }
4241
4273
  if (input === "l" && hasMore) {
4242
- doFetch(searchText, issues.length, true);
4274
+ doFetch(searchText, assigneeFilter, issues.length, true);
4243
4275
  return;
4244
4276
  }
4245
4277
  if (input === "r") {
4246
- doFetch(searchText);
4278
+ doFetch(searchText, assigneeFilter);
4247
4279
  }
4248
4280
  },
4249
4281
  { isActive }
@@ -4260,6 +4292,7 @@ function JiraSavedViewBrowserBox({
4260
4292
  auth,
4261
4293
  myAccountId,
4262
4294
  myDisplayName,
4295
+ onFetchCurrentUser,
4263
4296
  isActive,
4264
4297
  onClose: () => setDetailIssue(null),
4265
4298
  onIssueUpdated: handleIssueUpdated,
@@ -4276,7 +4309,7 @@ function JiraSavedViewBrowserBox({
4276
4309
  /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
4277
4310
  " ",
4278
4311
  "(",
4279
- filteredIssues.length,
4312
+ issues.length,
4280
4313
  "/",
4281
4314
  total,
4282
4315
  ")"
@@ -4286,12 +4319,11 @@ function JiraSavedViewBrowserBox({
4286
4319
  /* @__PURE__ */ jsxs10(Box10, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
4287
4320
  !view && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Select a view to browse issues" }) }),
4288
4321
  view && loading && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
4289
- /* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
4322
+ /* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
4290
4323
  " Loading issues..."
4291
4324
  ] }) }),
4292
4325
  view && error && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { color: "red", children: error }) }),
4293
- view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: searchText ? "No issues match search" : "No issues found" }) }),
4294
- view && !loading && !error && filteredIssues.length === 0 && issues.length > 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No issues match filter" }) }),
4326
+ view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: hasActiveFilters ? "No issues match filter" : "No issues found" }) }),
4295
4327
  rows.length > 0 && /* @__PURE__ */ jsx12(ScrollView6, { ref: scrollRef, children: rows.map((row, rowIdx) => {
4296
4328
  if (row.type === "header") {
4297
4329
  const stateLabel = row.state === "active" ? " (active)" : "";
@@ -4456,6 +4488,7 @@ function JiraBrowserView({
4456
4488
  const [myDisplayName, setMyDisplayName] = useState17(null);
4457
4489
  const [inputModeActive, setInputModeActive] = useState17(false);
4458
4490
  const lastRepoRef = useRef7(null);
4491
+ const fetchingUserRef = useRef7(false);
4459
4492
  const auth = useMemo3(() => {
4460
4493
  if (!repo.repoPath || !isJiraConfigured(repo.repoPath)) return null;
4461
4494
  const siteUrl = getJiraSiteUrl(repo.repoPath);
@@ -4473,18 +4506,21 @@ function JiraBrowserView({
4473
4506
  setSelectedViewId(loaded[0].id);
4474
4507
  }
4475
4508
  }, [repo.repoPath]);
4476
- useEffect13(() => {
4477
- if (!auth) {
4478
- setMyAccountId(null);
4479
- return;
4509
+ const fetchCurrentUser = useCallback10(async () => {
4510
+ if (myAccountId && myDisplayName) {
4511
+ return { accountId: myAccountId, displayName: myDisplayName };
4480
4512
  }
4481
- getCurrentUser(auth).then((result) => {
4482
- if (result.success) {
4483
- setMyAccountId(result.data.accountId);
4484
- setMyDisplayName(result.data.displayName);
4485
- }
4486
- });
4487
- }, [auth == null ? void 0 : auth.siteUrl, auth == null ? void 0 : auth.email]);
4513
+ if (!auth || fetchingUserRef.current) return null;
4514
+ fetchingUserRef.current = true;
4515
+ const result = await getCurrentUser(auth);
4516
+ fetchingUserRef.current = false;
4517
+ if (result.success) {
4518
+ setMyAccountId(result.data.accountId);
4519
+ setMyDisplayName(result.data.displayName);
4520
+ return { accountId: result.data.accountId, displayName: result.data.displayName };
4521
+ }
4522
+ return null;
4523
+ }, [auth, myAccountId, myDisplayName]);
4488
4524
  useEffect13(() => {
4489
4525
  onModalChange == null ? void 0 : onModalChange(modal.isOpen || inputModeActive);
4490
4526
  }, [modal.isOpen, inputModeActive, onModalChange]);
@@ -4547,7 +4583,7 @@ function JiraBrowserView({
4547
4583
  if (input === "5") onFocusedBoxChange("saved-views");
4548
4584
  if (input === "6") onFocusedBoxChange("browser");
4549
4585
  },
4550
- { isActive: isActive && !modal.isOpen }
4586
+ { isActive: isActive && !modal.isOpen && !inputModeActive }
4551
4587
  );
4552
4588
  if (modal.type === "add") {
4553
4589
  return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx14(
@@ -4586,6 +4622,7 @@ function JiraBrowserView({
4586
4622
  auth,
4587
4623
  myAccountId,
4588
4624
  myDisplayName,
4625
+ onFetchCurrentUser: fetchCurrentUser,
4589
4626
  isActive: isActive && focusedBox === "browser",
4590
4627
  onInputModeChange: setInputModeActive,
4591
4628
  onLogUpdated
@@ -5337,9 +5374,371 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
5337
5374
  ] });
5338
5375
  }
5339
5376
 
5377
+ // src/components/pull-requests/AllPullRequestsView.tsx
5378
+ import open6 from "open";
5379
+ import { useCallback as useCallback11, useEffect as useEffect18, useState as useState22 } from "react";
5380
+ import { TitledBox as TitledBox9 } from "@mishieck/ink-titled-box";
5381
+ import { Box as Box21, Text as Text20, useInput as useInput18 } from "ink";
5382
+ import { ScrollView as ScrollView11 } from "ink-scroll-view";
5383
+ import Spinner4 from "ink-spinner";
5384
+
5385
+ // src/lib/github/pr-list.ts
5386
+ import { exec as exec4 } from "child_process";
5387
+ import { promisify as promisify2 } from "util";
5388
+ var execAsync2 = promisify2(exec4);
5389
+ async function listPRs(repo, opts) {
5390
+ if (!await isGhInstalled()) {
5391
+ return {
5392
+ success: false,
5393
+ error: "GitHub CLI (gh) is not installed. Install from https://cli.github.com",
5394
+ errorType: "not_installed"
5395
+ };
5396
+ }
5397
+ if (!await isGhAuthenticated()) {
5398
+ return {
5399
+ success: false,
5400
+ error: "Not authenticated. Run 'gh auth login' to authenticate.",
5401
+ errorType: "not_authenticated"
5402
+ };
5403
+ }
5404
+ const fields = "number,title,state,author,createdAt,isDraft,reviewDecision,statusCheckRollup";
5405
+ const state = (opts == null ? void 0 : opts.state) ?? "open";
5406
+ const limit = (opts == null ? void 0 : opts.limit) ?? 30;
5407
+ const args = [`gh pr list`, `--state ${state}`, `--limit ${limit}`, `--json ${fields}`, `--repo "${repo}"`];
5408
+ if (opts == null ? void 0 : opts.search) {
5409
+ args.push(`--search "${opts.search.replace(/"/g, '\\"')}"`);
5410
+ }
5411
+ try {
5412
+ const { stdout } = await execAsync2(`${args.join(" ")} 2>/dev/null`);
5413
+ const prs = JSON.parse(stdout);
5414
+ return { success: true, data: prs };
5415
+ } catch {
5416
+ return { success: false, error: "Failed to fetch PRs", errorType: "api_error" };
5417
+ }
5418
+ }
5419
+ async function checkoutPR(prNumber, repo) {
5420
+ if (!await isGhInstalled()) {
5421
+ return { success: false, error: "gh CLI not installed", errorType: "not_installed" };
5422
+ }
5423
+ if (!await isGhAuthenticated()) {
5424
+ return { success: false, error: "gh CLI not authenticated", errorType: "not_authenticated" };
5425
+ }
5426
+ try {
5427
+ await execAsync2(`gh pr checkout ${prNumber} --repo "${repo}" 2>&1`);
5428
+ return { success: true, data: `Checked out PR #${prNumber}` };
5429
+ } catch (e) {
5430
+ const msg = e instanceof Error ? e.message.split("\n").pop() ?? e.message : "Failed to checkout PR";
5431
+ return { success: false, error: msg, errorType: "api_error" };
5432
+ }
5433
+ }
5434
+
5435
+ // src/components/pull-requests/AllPullRequestsView.tsx
5436
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
5437
+ var STATE_CYCLE = ["open", "closed", "all"];
5438
+ function computeOverallCheck(checks) {
5439
+ if (!checks || checks.length === 0) return null;
5440
+ const statuses = checks.map(resolveCheckStatus);
5441
+ if (statuses.some((s) => s === "failure")) return "failure";
5442
+ if (statuses.some((s) => s === "pending")) return "pending";
5443
+ return "success";
5444
+ }
5445
+ function AllPullRequestsView({ isActive, onModalChange }) {
5446
+ const repo = useGitRepo();
5447
+ const [prs, setPrs] = useState22([]);
5448
+ const [loading, setLoading] = useState22(false);
5449
+ const [error, setError] = useState22(null);
5450
+ const [detailPR, setDetailPR] = useState22(null);
5451
+ const [prDetails, setPrDetails] = useState22(null);
5452
+ const [detailsLoading, setDetailsLoading] = useState22(false);
5453
+ const [detailsError, setDetailsError] = useState22(void 0);
5454
+ const [stateFilter, setStateFilter] = useState22("open");
5455
+ const [searchText, setSearchText] = useState22("");
5456
+ const [inputText, setInputText] = useState22("");
5457
+ const [limit, setLimit] = useState22(30);
5458
+ const [isSearching, setIsSearching] = useState22(false);
5459
+ const [checkoutLoading, setCheckoutLoading] = useState22(false);
5460
+ const [checkoutResult, setCheckoutResult] = useState22(null);
5461
+ useEffect18(() => {
5462
+ onModalChange == null ? void 0 : onModalChange(isSearching || detailPR !== null);
5463
+ }, [isSearching, detailPR, onModalChange]);
5464
+ const doFetch = useCallback11(
5465
+ async (state, search, fetchLimit = 30) => {
5466
+ if (!repo.currentRepoSlug) return;
5467
+ setLoading(true);
5468
+ setError(null);
5469
+ setPrs([]);
5470
+ const result = await listPRs(repo.currentRepoSlug, {
5471
+ state,
5472
+ search: search || void 0,
5473
+ limit: fetchLimit
5474
+ });
5475
+ if (result.success) {
5476
+ setPrs(result.data);
5477
+ } else {
5478
+ setError(result.error);
5479
+ }
5480
+ setLoading(false);
5481
+ },
5482
+ [repo.currentRepoSlug]
5483
+ );
5484
+ useEffect18(() => {
5485
+ if (repo.currentRepoSlug) {
5486
+ setStateFilter("open");
5487
+ setSearchText("");
5488
+ setInputText("");
5489
+ doFetch("open", "");
5490
+ }
5491
+ }, [repo.currentRepoSlug]);
5492
+ const fetchDetails = useCallback11(
5493
+ async (pr) => {
5494
+ if (!repo.currentRepoSlug) return;
5495
+ setDetailsLoading(true);
5496
+ setDetailsError(void 0);
5497
+ const result = await getPRDetails(pr.number, repo.currentRepoSlug);
5498
+ if (result.success) {
5499
+ setPrDetails(result.data);
5500
+ setDetailsError(void 0);
5501
+ } else {
5502
+ setDetailsError(result.error);
5503
+ }
5504
+ setDetailsLoading(false);
5505
+ },
5506
+ [repo.currentRepoSlug]
5507
+ );
5508
+ const handleSelect = useCallback11(
5509
+ (index) => {
5510
+ const pr = prs[index];
5511
+ if (pr) {
5512
+ setDetailPR(pr);
5513
+ fetchDetails(pr);
5514
+ }
5515
+ },
5516
+ [prs, fetchDetails]
5517
+ );
5518
+ const { highlightedIndex, scrollRef } = useListNavigation({
5519
+ items: prs,
5520
+ selectedIndex: detailPR ? prs.findIndex((p) => p.number === detailPR.number) : -1,
5521
+ onSelect: handleSelect,
5522
+ isActive: isActive && !detailPR && !isSearching
5523
+ });
5524
+ const currentPR = prs[highlightedIndex] ?? null;
5525
+ const getPRUrl = (pr) => {
5526
+ if (!repo.currentRepoSlug) return null;
5527
+ return `https://github.com/${repo.currentRepoSlug}/pull/${pr.number}`;
5528
+ };
5529
+ const hasActiveFilters = stateFilter !== "open" || searchText.length > 0;
5530
+ useInput18(
5531
+ (input, key) => {
5532
+ if (isSearching) {
5533
+ if (key.escape) {
5534
+ setIsSearching(false);
5535
+ setInputText(searchText);
5536
+ return;
5537
+ }
5538
+ if (key.return) {
5539
+ setIsSearching(false);
5540
+ const newSearch = inputText.trim();
5541
+ if (newSearch !== searchText) {
5542
+ setSearchText(newSearch);
5543
+ setLimit(30);
5544
+ doFetch(stateFilter, newSearch);
5545
+ }
5546
+ return;
5547
+ }
5548
+ if (key.backspace || key.delete) {
5549
+ setInputText((prev) => prev.slice(0, -1));
5550
+ return;
5551
+ }
5552
+ if (input && input.length > 0) {
5553
+ const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
5554
+ if (printable.length > 0) {
5555
+ setInputText((prev) => prev + printable);
5556
+ }
5557
+ return;
5558
+ }
5559
+ return;
5560
+ }
5561
+ if (input === "/") {
5562
+ setIsSearching(true);
5563
+ setInputText(searchText);
5564
+ return;
5565
+ }
5566
+ if (input === "s") {
5567
+ const idx = STATE_CYCLE.indexOf(stateFilter);
5568
+ const newState = STATE_CYCLE[(idx + 1) % STATE_CYCLE.length];
5569
+ setStateFilter(newState);
5570
+ setLimit(30);
5571
+ doFetch(newState, searchText);
5572
+ return;
5573
+ }
5574
+ if (input === "x") {
5575
+ setStateFilter("open");
5576
+ setSearchText("");
5577
+ setInputText("");
5578
+ setLimit(30);
5579
+ doFetch("open", "");
5580
+ return;
5581
+ }
5582
+ if (input === "o" && currentPR) {
5583
+ const url = getPRUrl(currentPR);
5584
+ if (url) open6(url).catch(() => {
5585
+ });
5586
+ }
5587
+ if (input === "y" && currentPR) {
5588
+ const url = getPRUrl(currentPR);
5589
+ if (url) copyToClipboard(url);
5590
+ }
5591
+ if (input === "l") {
5592
+ const newLimit = limit + 30;
5593
+ setLimit(newLimit);
5594
+ doFetch(stateFilter, searchText, newLimit);
5595
+ return;
5596
+ }
5597
+ if (input === "r") {
5598
+ doFetch(stateFilter, searchText, limit);
5599
+ }
5600
+ },
5601
+ { isActive: isActive && !detailPR }
5602
+ );
5603
+ useInput18(
5604
+ (input, key) => {
5605
+ if (key.escape) {
5606
+ setDetailPR(null);
5607
+ setCheckoutResult(null);
5608
+ return;
5609
+ }
5610
+ if (input === "y" && detailPR) {
5611
+ const url = getPRUrl(detailPR);
5612
+ if (url) copyToClipboard(url);
5613
+ }
5614
+ if (input === "c" && detailPR && !checkoutLoading && repo.currentRepoSlug) {
5615
+ setCheckoutLoading(true);
5616
+ setCheckoutResult(null);
5617
+ checkoutPR(detailPR.number, repo.currentRepoSlug).then((result) => {
5618
+ setCheckoutLoading(false);
5619
+ if (result.success) {
5620
+ setCheckoutResult({ success: true, message: `Checked out #${detailPR.number}` });
5621
+ repo.refreshBranch();
5622
+ } else {
5623
+ setCheckoutResult({ success: false, message: result.error });
5624
+ }
5625
+ });
5626
+ }
5627
+ },
5628
+ { isActive: isActive && detailPR !== null }
5629
+ );
5630
+ const filterParts = [];
5631
+ if (stateFilter !== "open") filterParts.push(stateFilter);
5632
+ if (searchText) filterParts.push(`"${searchText}"`);
5633
+ const borderColor = isActive ? "yellow" : void 0;
5634
+ const stateColor = (pr) => {
5635
+ const display = resolveMergeDisplay({
5636
+ state: pr.state,
5637
+ isDraft: pr.isDraft,
5638
+ mergeable: "UNKNOWN"
5639
+ });
5640
+ return display.color;
5641
+ };
5642
+ const stateLabel = (pr) => {
5643
+ if (pr.isDraft) return "Draft";
5644
+ if (pr.state === "MERGED") return "Merged";
5645
+ if (pr.state === "CLOSED") return "Closed";
5646
+ return "Open";
5647
+ };
5648
+ if (detailPR) {
5649
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", flexGrow: 1, children: [
5650
+ /* @__PURE__ */ jsx23(
5651
+ PRDetailsBox,
5652
+ {
5653
+ pr: prDetails,
5654
+ loading: detailsLoading,
5655
+ error: detailsError,
5656
+ isActive,
5657
+ title: "[5] Pull Requests"
5658
+ }
5659
+ ),
5660
+ checkoutLoading ? /* @__PURE__ */ jsxs21(Text20, { color: "yellow", children: [
5661
+ " ",
5662
+ /* @__PURE__ */ jsx23(Spinner4, { type: "dots" }),
5663
+ " Checking out..."
5664
+ ] }) : checkoutResult ? /* @__PURE__ */ jsxs21(Text20, { color: checkoutResult.success ? "green" : "red", children: [
5665
+ " ",
5666
+ checkoutResult.message
5667
+ ] }) : /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: " Esc back \xB7 j/k scroll \xB7 o open \xB7 y copy \xB7 c checkout" })
5668
+ ] });
5669
+ }
5670
+ return /* @__PURE__ */ jsx23(Box21, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx23(TitledBox9, { borderStyle: "round", titles: ["[5] Pull Requests"], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
5671
+ (isSearching || hasActiveFilters) && /* @__PURE__ */ jsxs21(Box21, { children: [
5672
+ /* @__PURE__ */ jsx23(Text20, { color: "blue", children: "Filter: " }),
5673
+ isSearching ? /* @__PURE__ */ jsxs21(Fragment5, { children: [
5674
+ /* @__PURE__ */ jsx23(Text20, { children: inputText }),
5675
+ /* @__PURE__ */ jsx23(Text20, { backgroundColor: "yellow", children: " " })
5676
+ ] }) : /* @__PURE__ */ jsxs21(Fragment5, { children: [
5677
+ /* @__PURE__ */ jsx23(Text20, { children: filterParts.join(" + ") }),
5678
+ /* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
5679
+ " (",
5680
+ prs.length,
5681
+ ")"
5682
+ ] })
5683
+ ] })
5684
+ ] }),
5685
+ loading && /* @__PURE__ */ jsxs21(Text20, { color: "yellow", children: [
5686
+ /* @__PURE__ */ jsx23(Spinner4, { type: "dots" }),
5687
+ " Loading PRs..."
5688
+ ] }),
5689
+ error && /* @__PURE__ */ jsx23(Text20, { color: "red", children: error }),
5690
+ !loading && !error && prs.length === 0 && /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: hasActiveFilters ? "No PRs match filter" : "No open PRs" }),
5691
+ !loading && !error && prs.length > 0 && /* @__PURE__ */ jsx23(ScrollView11, { ref: scrollRef, children: prs.map((pr, idx) => {
5692
+ const isHighlighted = isActive && idx === highlightedIndex;
5693
+ const cursor = isHighlighted ? ">" : " ";
5694
+ const review = resolveReviewDisplay(pr.reviewDecision);
5695
+ const overallCheck = computeOverallCheck(pr.statusCheckRollup);
5696
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
5697
+ /* @__PURE__ */ jsxs21(Box21, { children: [
5698
+ /* @__PURE__ */ jsxs21(Text20, { color: isHighlighted ? "yellow" : void 0, children: [
5699
+ cursor,
5700
+ " "
5701
+ ] }),
5702
+ /* @__PURE__ */ jsxs21(Text20, { children: [
5703
+ "#",
5704
+ pr.number
5705
+ ] }),
5706
+ /* @__PURE__ */ jsxs21(Text20, { children: [
5707
+ " ",
5708
+ pr.title,
5709
+ " "
5710
+ ] }),
5711
+ (pr.state !== "OPEN" || pr.isDraft) && /* @__PURE__ */ jsxs21(Text20, { color: stateColor(pr), children: [
5712
+ "[",
5713
+ stateLabel(pr),
5714
+ "]"
5715
+ ] })
5716
+ ] }),
5717
+ /* @__PURE__ */ jsxs21(Box21, { children: [
5718
+ /* @__PURE__ */ jsx23(Text20, { children: " " }),
5719
+ /* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
5720
+ " ",
5721
+ pr.author.login,
5722
+ " \xB7 ",
5723
+ timeAgo(pr.createdAt)
5724
+ ] }),
5725
+ pr.reviewDecision && /* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
5726
+ " \xB7 ",
5727
+ review.text
5728
+ ] }),
5729
+ overallCheck && /* @__PURE__ */ jsxs21(Text20, { color: CHECK_COLORS[overallCheck], children: [
5730
+ " ",
5731
+ CHECK_ICONS[overallCheck]
5732
+ ] })
5733
+ ] })
5734
+ ] }, pr.number);
5735
+ }) })
5736
+ ] }) }) });
5737
+ }
5738
+
5340
5739
  // src/components/ui/KeybindingsBar.tsx
5341
- import { Box as Box21, Text as Text20 } from "ink";
5342
- import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
5740
+ import { Box as Box22, Text as Text21 } from "ink";
5741
+ import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
5343
5742
  var globalBindings = [
5344
5743
  { key: "1-4", label: "Focus" },
5345
5744
  { key: "Tab", label: "Switch Tab" },
@@ -5350,14 +5749,14 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
5350
5749
  var DUCK_ASCII = "<(')___";
5351
5750
  function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
5352
5751
  const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
5353
- return /* @__PURE__ */ jsxs21(Box21, { flexShrink: 0, paddingX: 1, gap: 2, children: [
5354
- allBindings.map((binding) => /* @__PURE__ */ jsxs21(Box21, { gap: 1, children: [
5355
- /* @__PURE__ */ jsx23(Text20, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
5356
- /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: binding.label })
5752
+ return /* @__PURE__ */ jsxs22(Box22, { flexShrink: 0, paddingX: 1, gap: 2, children: [
5753
+ allBindings.map((binding) => /* @__PURE__ */ jsxs22(Box22, { gap: 1, children: [
5754
+ /* @__PURE__ */ jsx24(Text21, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
5755
+ /* @__PURE__ */ jsx24(Text21, { dimColor: true, children: binding.label })
5357
5756
  ] }, binding.key)),
5358
- (duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs21(Box21, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
5359
- /* @__PURE__ */ jsx23(Text20, { children: DUCK_ASCII }),
5360
- /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: duck.message })
5757
+ (duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs22(Box22, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
5758
+ /* @__PURE__ */ jsx24(Text21, { children: DUCK_ASCII }),
5759
+ /* @__PURE__ */ jsx24(Text21, { dimColor: true, children: duck.message })
5361
5760
  ] })
5362
5761
  ] });
5363
5762
  }
@@ -5365,7 +5764,8 @@ function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
5365
5764
  // src/constants/tabs.ts
5366
5765
  var COLUMN2_TABS = [
5367
5766
  { id: "logs", label: "Logs" },
5368
- { id: "jira-browser", label: "Jira" }
5767
+ { id: "jira-browser", label: "Jira" },
5768
+ { id: "pull-requests", label: "PRs" }
5369
5769
  ];
5370
5770
 
5371
5771
  // src/constants/github.ts
@@ -5435,6 +5835,18 @@ var LOGS_KEYBINDINGS = {
5435
5835
  ]
5436
5836
  };
5437
5837
 
5838
+ // src/constants/pull-requests.ts
5839
+ var PULL_REQUESTS_KEYBINDINGS = [
5840
+ { key: "Space", label: "Details" },
5841
+ { key: "/", label: "Search" },
5842
+ { key: "s", label: "State" },
5843
+ { key: "l", label: "Load More" },
5844
+ { key: "x", label: "Clear Filters" },
5845
+ { key: "o", label: "Open", color: "green" },
5846
+ { key: "y", label: "Copy Link" },
5847
+ { key: "r", label: "Refresh" }
5848
+ ];
5849
+
5438
5850
  // src/lib/keybindings.ts
5439
5851
  function computeKeybindings(focusedView, state) {
5440
5852
  switch (focusedView) {
@@ -5448,39 +5860,53 @@ function computeKeybindings(focusedView, state) {
5448
5860
  case "jira-browser":
5449
5861
  if (state["jira-browser"].modalOpen) return [];
5450
5862
  return JIRA_BROWSER_KEYBINDINGS[state["jira-browser"].focusedBox];
5863
+ case "pull-requests":
5864
+ if (state["pull-requests"].modalOpen) return [];
5865
+ return PULL_REQUESTS_KEYBINDINGS;
5451
5866
  default:
5452
5867
  return [];
5453
5868
  }
5454
5869
  }
5455
5870
 
5456
5871
  // src/app.tsx
5457
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
5872
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
5458
5873
  function App() {
5459
5874
  const { exit } = useApp();
5460
- const [focusedView, setFocusedView] = useState22("github");
5461
- const [modalOpen, setModalOpen] = useState22(false);
5462
- const [logRefreshKey, setLogRefreshKey] = useState22(0);
5463
- const [activeTab, setActiveTab] = useState22("logs");
5875
+ const [focusedView, setFocusedView] = useState23("github");
5876
+ const [modalOpen, setModalOpen] = useState23(false);
5877
+ const [logRefreshKey, setLogRefreshKey] = useState23(0);
5878
+ const [activeTab, setActiveTab] = useState23("logs");
5464
5879
  const duck = useRubberDuck();
5465
- const [githubFocusedBox, setGithubFocusedBox] = useState22("remotes");
5466
- const [jiraState, setJiraState] = useState22("not_configured");
5467
- const [logsFocusedBox, setLogsFocusedBox] = useState22("history");
5468
- const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState22("saved-views");
5469
- const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState22(false);
5880
+ const [githubFocusedBox, setGithubFocusedBox] = useState23("remotes");
5881
+ const [jiraState, setJiraState] = useState23("not_configured");
5882
+ const [logsFocusedBox, setLogsFocusedBox] = useState23("history");
5883
+ const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState23("saved-views");
5884
+ const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState23(false);
5885
+ const [pullRequestsModalOpen, setPullRequestsModalOpen] = useState23(false);
5470
5886
  const keybindings = useMemo4(
5471
5887
  () => computeKeybindings(focusedView, {
5472
5888
  github: { focusedBox: githubFocusedBox },
5473
5889
  jira: { jiraState, modalOpen },
5474
5890
  logs: { focusedBox: logsFocusedBox },
5475
- "jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
5891
+ "jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen },
5892
+ "pull-requests": { modalOpen: pullRequestsModalOpen }
5476
5893
  }),
5477
- [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox, jiraBrowserFocusedBox, jiraBrowserModalOpen]
5894
+ [
5895
+ focusedView,
5896
+ githubFocusedBox,
5897
+ jiraState,
5898
+ modalOpen,
5899
+ logsFocusedBox,
5900
+ jiraBrowserFocusedBox,
5901
+ jiraBrowserModalOpen,
5902
+ pullRequestsModalOpen
5903
+ ]
5478
5904
  );
5479
- const handleLogUpdated = useCallback11(() => {
5905
+ const handleLogUpdated = useCallback12(() => {
5480
5906
  setLogRefreshKey((prev) => prev + 1);
5481
5907
  }, []);
5482
- const anyModalOpen = modalOpen || jiraBrowserModalOpen;
5483
- useInput18(
5908
+ const anyModalOpen = modalOpen || jiraBrowserModalOpen || pullRequestsModalOpen;
5909
+ useInput19(
5484
5910
  (input, key) => {
5485
5911
  if (key.ctrl && input === "c") {
5486
5912
  exit();
@@ -5519,17 +5945,17 @@ function App() {
5519
5945
  },
5520
5946
  { isActive: !anyModalOpen }
5521
5947
  );
5522
- return /* @__PURE__ */ jsxs22(Box22, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
5523
- /* @__PURE__ */ jsxs22(Box22, { height: 1, flexDirection: "row", columnGap: 1, children: [
5524
- /* @__PURE__ */ jsx24(Box22, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx24(Text21, { color: "gray", children: "Current branch" }) }),
5525
- /* @__PURE__ */ jsxs22(Box22, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
5526
- /* @__PURE__ */ jsx24(Text21, { color: "gray", children: "Dashboards" }),
5527
- COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx24(Text21, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
5948
+ return /* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
5949
+ /* @__PURE__ */ jsxs23(Box23, { height: 1, flexDirection: "row", columnGap: 1, children: [
5950
+ /* @__PURE__ */ jsx25(Box23, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx25(Text22, { color: "gray", children: "Current branch" }) }),
5951
+ /* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
5952
+ /* @__PURE__ */ jsx25(Text22, { color: "gray", children: "Dashboards" }),
5953
+ COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx25(Text22, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
5528
5954
  ] })
5529
5955
  ] }),
5530
- /* @__PURE__ */ jsxs22(Box22, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
5531
- /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
5532
- /* @__PURE__ */ jsx24(
5956
+ /* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
5957
+ /* @__PURE__ */ jsxs23(Box23, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
5958
+ /* @__PURE__ */ jsx25(
5533
5959
  GitHubView,
5534
5960
  {
5535
5961
  isActive: focusedView === "github",
@@ -5537,7 +5963,7 @@ function App() {
5537
5963
  onLogUpdated: handleLogUpdated
5538
5964
  }
5539
5965
  ),
5540
- /* @__PURE__ */ jsx24(
5966
+ /* @__PURE__ */ jsx25(
5541
5967
  JiraView,
5542
5968
  {
5543
5969
  isActive: focusedView === "jira",
@@ -5547,8 +5973,8 @@ function App() {
5547
5973
  }
5548
5974
  )
5549
5975
  ] }),
5550
- /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
5551
- activeTab === "logs" && /* @__PURE__ */ jsx24(
5976
+ /* @__PURE__ */ jsxs23(Box23, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
5977
+ activeTab === "logs" && /* @__PURE__ */ jsx25(
5552
5978
  LogsView,
5553
5979
  {
5554
5980
  isActive: focusedView === "logs",
@@ -5557,7 +5983,7 @@ function App() {
5557
5983
  onFocusedBoxChange: setLogsFocusedBox
5558
5984
  }
5559
5985
  ),
5560
- activeTab === "jira-browser" && /* @__PURE__ */ jsx24(
5986
+ activeTab === "jira-browser" && /* @__PURE__ */ jsx25(
5561
5987
  JiraBrowserView,
5562
5988
  {
5563
5989
  isActive: focusedView === "jira-browser",
@@ -5566,10 +5992,11 @@ function App() {
5566
5992
  onModalChange: setJiraBrowserModalOpen,
5567
5993
  onLogUpdated: handleLogUpdated
5568
5994
  }
5569
- )
5995
+ ),
5996
+ activeTab === "pull-requests" && /* @__PURE__ */ jsx25(AllPullRequestsView, { isActive: focusedView === "pull-requests", onModalChange: setPullRequestsModalOpen })
5570
5997
  ] })
5571
5998
  ] }),
5572
- /* @__PURE__ */ jsx24(
5999
+ /* @__PURE__ */ jsx25(
5573
6000
  KeybindingsBar,
5574
6001
  {
5575
6002
  contextBindings: keybindings,
@@ -5584,31 +6011,31 @@ function App() {
5584
6011
  import { render as inkRender } from "ink";
5585
6012
 
5586
6013
  // src/lib/Screen.tsx
5587
- import { useCallback as useCallback12, useEffect as useEffect18, useState as useState23 } from "react";
5588
- import { Box as Box23, useStdout as useStdout2 } from "ink";
5589
- import { jsx as jsx25 } from "react/jsx-runtime";
6014
+ import { useCallback as useCallback13, useEffect as useEffect19, useState as useState24 } from "react";
6015
+ import { Box as Box24, useStdout as useStdout2 } from "ink";
6016
+ import { jsx as jsx26 } from "react/jsx-runtime";
5590
6017
  function Screen({ children }) {
5591
6018
  const { stdout } = useStdout2();
5592
- const getSize = useCallback12(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
5593
- const [size, setSize] = useState23(getSize);
5594
- useEffect18(() => {
6019
+ const getSize = useCallback13(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
6020
+ const [size, setSize] = useState24(getSize);
6021
+ useEffect19(() => {
5595
6022
  const onResize = () => setSize(getSize());
5596
6023
  stdout.on("resize", onResize);
5597
6024
  return () => {
5598
6025
  stdout.off("resize", onResize);
5599
6026
  };
5600
6027
  }, [stdout, getSize]);
5601
- return /* @__PURE__ */ jsx25(Box23, { height: size.height, width: size.width, children });
6028
+ return /* @__PURE__ */ jsx26(Box24, { height: size.height, width: size.width, children });
5602
6029
  }
5603
6030
 
5604
6031
  // src/lib/render.tsx
5605
- import { jsx as jsx26 } from "react/jsx-runtime";
6032
+ import { jsx as jsx27 } from "react/jsx-runtime";
5606
6033
  var ENTER_ALT_BUFFER = "\x1B[?1049h";
5607
6034
  var EXIT_ALT_BUFFER = "\x1B[?1049l";
5608
6035
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
5609
6036
  function render(node, options) {
5610
6037
  process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
5611
- const element = /* @__PURE__ */ jsx26(Screen, { children: node });
6038
+ const element = /* @__PURE__ */ jsx27(Screen, { children: node });
5612
6039
  const instance = inkRender(element, options);
5613
6040
  setImmediate(() => instance.rerender(element));
5614
6041
  const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
@@ -5629,7 +6056,7 @@ function render(node, options) {
5629
6056
  }
5630
6057
 
5631
6058
  // src/cli.tsx
5632
- import { jsx as jsx27 } from "react/jsx-runtime";
6059
+ import { jsx as jsx28 } from "react/jsx-runtime";
5633
6060
  var cli = meow(
5634
6061
  `
5635
6062
  Usage
@@ -5662,4 +6089,4 @@ if (cli.flags.cwd) {
5662
6089
  process.exit(1);
5663
6090
  }
5664
6091
  }
5665
- render(/* @__PURE__ */ jsx27(App, {}));
6092
+ render(/* @__PURE__ */ jsx28(App, {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clairo",
3
- "version": "2.2.0",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",