clairo 1.2.2 → 2.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 +40 -5
  2. package/dist/cli.js +1629 -258
  3. package/package.json +3 -1
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 useCallback9, useMemo as useMemo2, useState as useState16 } from "react";
8
- import { Box as Box17, useApp, useInput as useInput13 } from "ink";
7
+ import { useCallback as useCallback11, useMemo as useMemo4, useState as useState21 } from "react";
8
+ import { Box as Box22, Text as Text19, useApp, useInput as useInput18 } from "ink";
9
9
 
10
10
  // src/components/github/GitHubView.tsx
11
11
  import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef5, useState as useState11 } from "react";
@@ -192,7 +192,7 @@ function timeAgo(dateStr) {
192
192
  }
193
193
  function resolveCheckStatus(check) {
194
194
  const conclusion = check.conclusion ?? check.state;
195
- if (conclusion === "SUCCESS" || check.status === "COMPLETED") return "success";
195
+ if (conclusion === "SUCCESS") return "success";
196
196
  if (conclusion === "FAILURE" || conclusion === "ERROR") return "failure";
197
197
  if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "skipped";
198
198
  if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
@@ -209,18 +209,19 @@ var CHECK_ICONS = { success: "\u2713", failure: "\u2717", pending: "\u25CF", ski
209
209
  var CHECK_SORT_ORDER = { failure: 0, pending: 1, skipped: 2, success: 3 };
210
210
  function resolveReviewDisplay(reviewDecision) {
211
211
  const status = reviewDecision ?? "PENDING";
212
- if (status === "APPROVED") return { text: status, color: "green" };
213
- if (status === "CHANGES_REQUESTED") return { text: status, color: "red" };
214
- return { text: status, color: "yellow" };
212
+ if (status === "APPROVED") return { text: "Approved", color: "green" };
213
+ if (status === "CHANGES_REQUESTED") return { text: "Changes Requested", color: "red" };
214
+ if (status === "REVIEW_REQUIRED") return { text: "Review Required", color: "yellow" };
215
+ return { text: "Pending", color: "yellow" };
215
216
  }
216
217
  function resolveMergeDisplay(pr) {
217
- if (!pr) return { text: "UNKNOWN", color: "yellow" };
218
- if (pr.state === "MERGED") return { text: "MERGED", color: "magenta" };
219
- if (pr.state === "CLOSED") return { text: "CLOSED", color: "red" };
220
- if (pr.isDraft) return { text: "DRAFT", color: "yellow" };
221
- if (pr.mergeable === "MERGEABLE") return { text: "MERGEABLE", color: "green" };
222
- if (pr.mergeable === "CONFLICTING") return { text: "CONFLICTING", color: "red" };
223
- return { text: pr.mergeable ?? "UNKNOWN", color: "yellow" };
218
+ if (!pr) return { text: "Unknown", color: "yellow" };
219
+ if (pr.state === "MERGED") return { text: "Merged", color: "magenta" };
220
+ if (pr.state === "CLOSED") return { text: "Closed", color: "red" };
221
+ if (pr.isDraft) return { text: "Draft", color: "yellow" };
222
+ if (pr.mergeable === "MERGEABLE") return { text: "Open", color: "green" };
223
+ if (pr.mergeable === "CONFLICTING") return { text: "Conflicts", color: "red" };
224
+ return { text: "Unknown", color: "yellow" };
224
225
  }
225
226
  async function isGhInstalled() {
226
227
  try {
@@ -865,7 +866,8 @@ function createAuthHeader(email, apiToken) {
865
866
  return `Basic ${credentials}`;
866
867
  }
867
868
  async function jiraFetch(auth, endpoint, options) {
868
- const url = `${auth.siteUrl}/rest/api/3${endpoint}`;
869
+ const prefix = (options == null ? void 0 : options.apiPrefix) ?? "/rest/api/3";
870
+ const url = `${auth.siteUrl}${prefix}${endpoint}`;
869
871
  const method = (options == null ? void 0 : options.method) ?? "GET";
870
872
  try {
871
873
  const headers = {
@@ -910,6 +912,21 @@ async function validateCredentials(auth) {
910
912
  }
911
913
  return { success: true, data: result.data };
912
914
  }
915
+ async function getCurrentUser(auth) {
916
+ const result = await jiraFetch(auth, "/myself");
917
+ if (!result.ok) {
918
+ if (result.status === 401 || result.status === 403) {
919
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
920
+ }
921
+ return {
922
+ success: false,
923
+ error: result.error ?? "Failed to fetch current user",
924
+ errorType: "api_error"
925
+ };
926
+ }
927
+ const data = result.data;
928
+ return { success: true, data: { accountId: data.accountId, displayName: data.displayName } };
929
+ }
913
930
  async function getIssue(auth, ticketKey) {
914
931
  const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
915
932
  if (!result.ok) {
@@ -989,6 +1006,377 @@ async function applyTransition(auth, ticketKey, transitionId) {
989
1006
  }
990
1007
  return { success: true, data: null };
991
1008
  }
1009
+ async function getIssueDetail(auth, issueKey) {
1010
+ const result = await jiraFetch(auth, `/issue/${issueKey}?fields=summary,status,description,assignee,comment`);
1011
+ if (!result.ok) {
1012
+ if (result.status === 401 || result.status === 403) {
1013
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1014
+ }
1015
+ if (result.status === 404) {
1016
+ return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
1017
+ }
1018
+ return {
1019
+ success: false,
1020
+ error: result.error ?? "Failed to fetch issue details",
1021
+ errorType: "api_error"
1022
+ };
1023
+ }
1024
+ return { success: true, data: result.data };
1025
+ }
1026
+ async function assignIssue(auth, issueKey, accountId) {
1027
+ const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
1028
+ method: "PUT",
1029
+ body: { accountId }
1030
+ });
1031
+ if (!result.ok) {
1032
+ if (result.status === 401 || result.status === 403) {
1033
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1034
+ }
1035
+ if (result.status === 404) {
1036
+ return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
1037
+ }
1038
+ return {
1039
+ success: false,
1040
+ error: result.error ?? "Failed to assign issue",
1041
+ errorType: "api_error"
1042
+ };
1043
+ }
1044
+ return { success: true, data: null };
1045
+ }
1046
+ async function unassignIssue(auth, issueKey) {
1047
+ const result = await jiraFetch(auth, `/issue/${issueKey}/assignee`, {
1048
+ method: "PUT",
1049
+ body: { accountId: null }
1050
+ });
1051
+ if (!result.ok) {
1052
+ if (result.status === 401 || result.status === 403) {
1053
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1054
+ }
1055
+ if (result.status === 404) {
1056
+ return { success: false, error: `Issue ${issueKey} not found`, errorType: "invalid_ticket" };
1057
+ }
1058
+ return {
1059
+ success: false,
1060
+ error: result.error ?? "Failed to unassign issue",
1061
+ errorType: "api_error"
1062
+ };
1063
+ }
1064
+ return { success: true, data: null };
1065
+ }
1066
+
1067
+ // src/lib/jira/search.ts
1068
+ async function searchIssues(auth, jql, opts) {
1069
+ const params = new URLSearchParams({
1070
+ jql,
1071
+ fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
1072
+ startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
1073
+ maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
1074
+ });
1075
+ const result = await jiraFetch(auth, `/search?${params.toString()}`);
1076
+ if (!result.ok) {
1077
+ if (result.status === 401 || result.status === 403) {
1078
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1079
+ }
1080
+ return {
1081
+ success: false,
1082
+ error: result.error ?? "Failed to search issues",
1083
+ errorType: "api_error"
1084
+ };
1085
+ }
1086
+ return { success: true, data: result.data };
1087
+ }
1088
+ async function getFilterJql(auth, filterId) {
1089
+ const result = await jiraFetch(auth, `/filter/${filterId}`);
1090
+ if (!result.ok) {
1091
+ if (result.status === 401 || result.status === 403) {
1092
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1093
+ }
1094
+ if (result.status === 404) {
1095
+ return { success: false, error: `Filter ${filterId} not found`, errorType: "api_error" };
1096
+ }
1097
+ return {
1098
+ success: false,
1099
+ error: result.error ?? "Failed to fetch filter",
1100
+ errorType: "api_error"
1101
+ };
1102
+ }
1103
+ const data = result.data;
1104
+ return { success: true, data: data.jql };
1105
+ }
1106
+ async function getBoardIssues(auth, boardId, opts) {
1107
+ const params = new URLSearchParams({
1108
+ fields: "summary,status,assignee,priority,issuetype,sprint,closedSprints",
1109
+ startAt: String((opts == null ? void 0 : opts.startAt) ?? 0),
1110
+ maxResults: String((opts == null ? void 0 : opts.maxResults) ?? 50)
1111
+ });
1112
+ if (opts == null ? void 0 : opts.jql) {
1113
+ params.set("jql", opts.jql);
1114
+ }
1115
+ const result = await jiraFetch(auth, `/board/${boardId}/issue?${params.toString()}`, {
1116
+ apiPrefix: "/rest/agile/1.0"
1117
+ });
1118
+ if (!result.ok) {
1119
+ if (result.status === 401 || result.status === 403) {
1120
+ return { success: false, error: "Authentication failed", errorType: "auth_error" };
1121
+ }
1122
+ if (result.status === 404) {
1123
+ return { success: false, error: `Board ${boardId} not found`, errorType: "api_error" };
1124
+ }
1125
+ return {
1126
+ success: false,
1127
+ error: result.error ?? "Failed to fetch board issues",
1128
+ errorType: "api_error"
1129
+ };
1130
+ }
1131
+ return { success: true, data: result.data };
1132
+ }
1133
+ function escapeJql(text) {
1134
+ return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1135
+ }
1136
+ function appendTextSearch(jql, searchText) {
1137
+ const escaped = escapeJql(searchText);
1138
+ return `(${jql}) AND text ~ "${escaped}"`;
1139
+ }
1140
+ async function fetchViewIssues(auth, view, opts) {
1141
+ const { searchText, ...pageOpts } = opts ?? {};
1142
+ switch (view.source.type) {
1143
+ case "jql": {
1144
+ const jql = searchText ? appendTextSearch(view.source.jql, searchText) : view.source.jql;
1145
+ return searchIssues(auth, jql, pageOpts);
1146
+ }
1147
+ case "filter": {
1148
+ const filterResult = await getFilterJql(auth, view.source.filterId);
1149
+ if (!filterResult.success) return filterResult;
1150
+ const jql = searchText ? appendTextSearch(filterResult.data, searchText) : filterResult.data;
1151
+ return searchIssues(auth, jql, pageOpts);
1152
+ }
1153
+ case "board": {
1154
+ const jql = searchText ? `text ~ "${escapeJql(searchText)}"` : void 0;
1155
+ return getBoardIssues(auth, view.source.boardId, { ...pageOpts, jql });
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ // src/lib/jira/views.ts
1161
+ function getSavedViews(repoPath) {
1162
+ const config = getRepoConfig(repoPath);
1163
+ return config.savedJiraViews ?? [];
1164
+ }
1165
+ function addSavedView(repoPath, name, url, source) {
1166
+ const views = getSavedViews(repoPath);
1167
+ const view = {
1168
+ id: Date.now().toString(36),
1169
+ name,
1170
+ url,
1171
+ source,
1172
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1173
+ };
1174
+ updateRepoConfig(repoPath, { savedJiraViews: [...views, view] });
1175
+ return view;
1176
+ }
1177
+ function renameSavedView(repoPath, viewId, newName) {
1178
+ const views = getSavedViews(repoPath);
1179
+ updateRepoConfig(repoPath, {
1180
+ savedJiraViews: views.map((v) => v.id === viewId ? { ...v, name: newName } : v)
1181
+ });
1182
+ }
1183
+ function removeSavedView(repoPath, viewId) {
1184
+ const views = getSavedViews(repoPath);
1185
+ updateRepoConfig(repoPath, {
1186
+ savedJiraViews: views.filter((v) => v.id !== viewId)
1187
+ });
1188
+ }
1189
+
1190
+ // src/lib/jira/url-parser.ts
1191
+ function parseJiraUrl(input) {
1192
+ const trimmed = input.trim();
1193
+ let url;
1194
+ try {
1195
+ url = new URL(trimmed);
1196
+ } catch {
1197
+ return null;
1198
+ }
1199
+ const filterId = url.searchParams.get("filter");
1200
+ if (filterId && /^\d+$/.test(filterId)) {
1201
+ return { type: "filter", filterId };
1202
+ }
1203
+ const jql = url.searchParams.get("jql");
1204
+ if (jql) {
1205
+ return { type: "jql", jql };
1206
+ }
1207
+ const boardMatch = url.pathname.match(/\/boards\/(\d+)/);
1208
+ if (boardMatch) {
1209
+ return { type: "board", boardId: boardMatch[1] };
1210
+ }
1211
+ return null;
1212
+ }
1213
+ function generateViewName(input) {
1214
+ const source = parseJiraUrl(input);
1215
+ if (!source) return "Jira View";
1216
+ switch (source.type) {
1217
+ case "filter":
1218
+ return `Filter #${source.filterId}`;
1219
+ case "jql":
1220
+ return truncate(source.jql, 40);
1221
+ case "board":
1222
+ return `Board #${source.boardId}`;
1223
+ }
1224
+ }
1225
+ function truncate(str, maxLen) {
1226
+ if (str.length <= maxLen) return str;
1227
+ return str.slice(0, maxLen - 1) + "\u2026";
1228
+ }
1229
+
1230
+ // src/lib/jira/adf-to-markdown.ts
1231
+ function adfToMarkdown(node) {
1232
+ if (!node || typeof node !== "object") return "";
1233
+ return processNode(node).trimEnd();
1234
+ }
1235
+ function processNode(node, indent = "") {
1236
+ var _a, _b, _c, _d, _e, _f, _g;
1237
+ switch (node.type) {
1238
+ case "doc":
1239
+ return processChildren(node, indent);
1240
+ case "paragraph":
1241
+ return processChildren(node, indent) + "\n\n";
1242
+ case "heading": {
1243
+ const level = ((_a = node.attrs) == null ? void 0 : _a.level) ?? 1;
1244
+ const prefix = "#".repeat(level);
1245
+ return `${prefix} ${processChildren(node, indent)}
1246
+
1247
+ `;
1248
+ }
1249
+ case "text":
1250
+ return applyMarks(node.text ?? "", node.marks);
1251
+ case "hardBreak":
1252
+ return "\n";
1253
+ case "bulletList":
1254
+ return processList(node, indent, "bullet") + "\n";
1255
+ case "orderedList":
1256
+ return processList(node, indent, "ordered") + "\n";
1257
+ case "listItem":
1258
+ return processChildren(node, indent);
1259
+ case "codeBlock": {
1260
+ const lang = ((_b = node.attrs) == null ? void 0 : _b.language) ?? "";
1261
+ const code = processChildren(node, indent);
1262
+ return `\`\`\`${lang}
1263
+ ${code}
1264
+ \`\`\`
1265
+
1266
+ `;
1267
+ }
1268
+ case "blockquote": {
1269
+ const content = processChildren(node, indent);
1270
+ return content.split("\n").map((line) => `> ${line}`).join("\n") + "\n\n";
1271
+ }
1272
+ case "rule":
1273
+ return "---\n\n";
1274
+ case "mention":
1275
+ return `@${((_c = node.attrs) == null ? void 0 : _c.text) ?? "unknown"}`;
1276
+ case "emoji":
1277
+ return ((_d = node.attrs) == null ? void 0 : _d.shortName) ?? ((_e = node.attrs) == null ? void 0 : _e.text) ?? "";
1278
+ case "inlineCard": {
1279
+ const url = (_f = node.attrs) == null ? void 0 : _f.url;
1280
+ return url ? `[${url}](${url})` : "";
1281
+ }
1282
+ case "mediaSingle":
1283
+ case "mediaGroup":
1284
+ return processChildren(node, indent);
1285
+ case "media":
1286
+ return "[Attachment]\n\n";
1287
+ case "table":
1288
+ return processTable(node) + "\n";
1289
+ case "panel": {
1290
+ const panelType = ((_g = node.attrs) == null ? void 0 : _g.panelType) ?? "info";
1291
+ const label = panelType.charAt(0).toUpperCase() + panelType.slice(1);
1292
+ return `[${label}] ${processChildren(node, indent)}
1293
+ `;
1294
+ }
1295
+ default:
1296
+ if (node.content) {
1297
+ return processChildren(node, indent);
1298
+ }
1299
+ return node.text ?? "";
1300
+ }
1301
+ }
1302
+ function processChildren(node, indent = "") {
1303
+ if (!node.content) return "";
1304
+ return node.content.map((child) => processNode(child, indent)).join("");
1305
+ }
1306
+ function processList(node, indent, style) {
1307
+ if (!node.content) return "";
1308
+ return node.content.map((item, idx) => {
1309
+ const prefix = style === "bullet" ? "- " : `${idx + 1}. `;
1310
+ const childContent = (item.content ?? []).map((child) => {
1311
+ if (child.type === "bulletList" || child.type === "orderedList") {
1312
+ return processList(child, indent + " ", child.type === "bulletList" ? "bullet" : "ordered");
1313
+ }
1314
+ if (child.type === "paragraph") {
1315
+ return processChildren(child, indent);
1316
+ }
1317
+ return processNode(child, indent + " ");
1318
+ }).join("");
1319
+ return `${indent}${prefix}${childContent}`;
1320
+ }).join("\n");
1321
+ }
1322
+ function processTable(node) {
1323
+ if (!node.content) return "";
1324
+ const rows = [];
1325
+ let hasHeader = false;
1326
+ for (const row of node.content) {
1327
+ if (row.type !== "tableRow") continue;
1328
+ const cells = [];
1329
+ for (const cell of row.content ?? []) {
1330
+ if (cell.type === "tableHeader") hasHeader = true;
1331
+ const text = processChildren(cell).replace(/\n/g, " ").trim();
1332
+ cells.push(text);
1333
+ }
1334
+ rows.push(cells);
1335
+ }
1336
+ if (rows.length === 0) return "";
1337
+ const colCount = Math.max(...rows.map((r) => r.length));
1338
+ const lines = [];
1339
+ for (let i = 0; i < rows.length; i++) {
1340
+ const row = rows[i];
1341
+ while (row.length < colCount) row.push("");
1342
+ lines.push(`| ${row.join(" | ")} |`);
1343
+ if (i === 0 && hasHeader) {
1344
+ lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
1345
+ }
1346
+ }
1347
+ return lines.join("\n") + "\n";
1348
+ }
1349
+ function applyMarks(text, marks) {
1350
+ var _a;
1351
+ if (!marks || marks.length === 0) return text;
1352
+ let result = text;
1353
+ for (const mark of marks) {
1354
+ switch (mark.type) {
1355
+ case "strong":
1356
+ result = `**${result}**`;
1357
+ break;
1358
+ case "em":
1359
+ result = `*${result}*`;
1360
+ break;
1361
+ case "code":
1362
+ result = `\`${result}\``;
1363
+ break;
1364
+ case "strike":
1365
+ result = `~~${result}~~`;
1366
+ break;
1367
+ case "link": {
1368
+ const href = (_a = mark.attrs) == null ? void 0 : _a.href;
1369
+ if (href) {
1370
+ result = `[${result}](${href})`;
1371
+ }
1372
+ break;
1373
+ }
1374
+ case "underline":
1375
+ break;
1376
+ }
1377
+ }
1378
+ return result;
1379
+ }
992
1380
 
993
1381
  // src/lib/logs/index.ts
994
1382
  import { spawnSync } from "child_process";
@@ -1115,6 +1503,18 @@ function logJiraStatusChanged(ticketKey, ticketName, oldStatus, newStatus) {
1115
1503
  ${ticketKey}: ${ticketName}
1116
1504
  ${oldStatus} \u2192 ${newStatus}
1117
1505
 
1506
+ `;
1507
+ appendToLog(today, entry);
1508
+ }
1509
+ function logJiraAssigneeChanged(ticketKey, ticketName, action, displayName) {
1510
+ const timestamp = formatTimestamp();
1511
+ const today = getTodayDate();
1512
+ const detail = action === "assigned" && displayName ? `Assigned to ${displayName}` : "Unassigned";
1513
+ const entry = `## ${timestamp} - Updated Jira ticket
1514
+
1515
+ ${ticketKey}: ${ticketName}
1516
+ ${detail}
1517
+
1118
1518
  `;
1119
1519
  appendToLog(today, entry);
1120
1520
  }
@@ -1338,7 +1738,11 @@ function PRDetailsBox({ pr, loading, error, isActive }) {
1338
1738
  pr.deletions
1339
1739
  ] })
1340
1740
  ] }),
1341
- (((_c = pr.labels) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: pr.labels.map((l) => l.name).join(", ") }),
1741
+ (((_c = pr.labels) == null ? void 0 : _c.length) ?? 0) > 0 && /* @__PURE__ */ jsx3(Box3, { gap: 1, children: pr.labels.map((l) => /* @__PURE__ */ jsxs2(Box3, { children: [
1742
+ /* @__PURE__ */ jsx3(Text2, { color: "gray", children: "\uE0B6" }),
1743
+ /* @__PURE__ */ jsx3(Text2, { color: "black", backgroundColor: "gray", children: l.name }),
1744
+ /* @__PURE__ */ jsx3(Text2, { color: "gray", children: "\uE0B4" })
1745
+ ] }, l.name)) }),
1342
1746
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Divider, {}) }),
1343
1747
  (((_d = pr.assignees) == null ? void 0 : _d.length) ?? 0) > 0 && /* @__PURE__ */ jsxs2(Box3, { marginTop: 1, children: [
1344
1748
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "Assignees: " }),
@@ -1654,13 +2058,13 @@ function useLogs() {
1654
2058
  import { useCallback as useCallback6, useState as useState7 } from "react";
1655
2059
  function useModal() {
1656
2060
  const [modalType, setModalType] = useState7("none");
1657
- const open4 = useCallback6((type) => setModalType(type), []);
2061
+ const open6 = useCallback6((type) => setModalType(type), []);
1658
2062
  const close = useCallback6(() => setModalType("none"), []);
1659
2063
  const isOpen = modalType !== "none";
1660
2064
  return {
1661
2065
  type: modalType,
1662
2066
  isOpen,
1663
- open: open4,
2067
+ open: open6,
1664
2068
  close
1665
2069
  };
1666
2070
  }
@@ -1766,7 +2170,9 @@ var REACTION_MESSAGES = {
1766
2170
  error: ["Uh oh...", "There there...", "*concerned quacking*", "Quack... not good."],
1767
2171
  "jira:transition": ["Ticket moving!", "Progress!", "Workflow in motion!"],
1768
2172
  "jira:linked": ["Ticket linked!", "Jira connection made!", "Tracking enabled!"],
1769
- "jira:configured": ["Jira ready!", "Integration complete!", "Connected to Jira!"]
2173
+ "jira:configured": ["Jira ready!", "Integration complete!", "Connected to Jira!"],
2174
+ "jira:assigned": ["Assigned!", "On it!", "Claimed!"],
2175
+ "jira:unassigned": ["Unassigned!", "Free agent!", "Released!"]
1770
2176
  };
1771
2177
  function useRubberDuck() {
1772
2178
  const [state, setState] = useState9({
@@ -2239,11 +2645,11 @@ ${body}`;
2239
2645
  ] });
2240
2646
  }
2241
2647
 
2242
- // src/components/jira/JiraView.tsx
2243
- import open3 from "open";
2244
- import { useEffect as useEffect10, useRef as useRef6 } from "react";
2648
+ // src/components/jira-browser/JiraBrowserView.tsx
2649
+ import { useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo3, useRef as useRef7, useState as useState16 } from "react";
2650
+ import { Box as Box12, useInput as useInput10 } from "ink";
2245
2651
 
2246
- // src/components/jira/LinkTicketModal.tsx
2652
+ // src/components/jira-browser/AddViewModal.tsx
2247
2653
  import { useState as useState12 } from "react";
2248
2654
  import { Box as Box8, Text as Text7, useInput as useInput6 } from "ink";
2249
2655
 
@@ -2262,8 +2668,11 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
2262
2668
  if (key.return || key.escape || key.upArrow || key.downArrow || key.tab) {
2263
2669
  return;
2264
2670
  }
2265
- if (input && input.length === 1 && input.charCodeAt(0) >= 32) {
2266
- onChange(value + input);
2671
+ if (input && input.length > 0) {
2672
+ const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
2673
+ if (printable.length > 0) {
2674
+ onChange(value + printable);
2675
+ }
2267
2676
  }
2268
2677
  },
2269
2678
  { isActive }
@@ -2276,11 +2685,13 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
2276
2685
  ] }) });
2277
2686
  }
2278
2687
 
2279
- // src/components/jira/LinkTicketModal.tsx
2688
+ // src/components/jira-browser/AddViewModal.tsx
2280
2689
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2281
- function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
2282
- const [ticketInput, setTicketInput] = useState12("");
2283
- const canSubmit = ticketInput.trim().length > 0;
2690
+ function AddViewModal({ onSubmit, onCancel, loading, error }) {
2691
+ const [url, setUrl] = useState12("");
2692
+ const [name, setName] = useState12("");
2693
+ const [activeField, setActiveField] = useState12("url");
2694
+ const canSubmit = url.trim().length > 0;
2284
2695
  useInput6(
2285
2696
  (_input, key) => {
2286
2697
  if (loading) return;
@@ -2288,120 +2699,1014 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
2288
2699
  onCancel();
2289
2700
  return;
2290
2701
  }
2702
+ if (key.tab) {
2703
+ setActiveField((f) => f === "url" ? "name" : "url");
2704
+ return;
2705
+ }
2291
2706
  if (key.return && canSubmit) {
2292
- onSubmit(ticketInput.trim());
2707
+ const viewName = name.trim() || generateViewName(url.trim());
2708
+ onSubmit(url.trim(), viewName);
2293
2709
  }
2294
2710
  },
2295
2711
  { isActive: !loading }
2296
2712
  );
2297
2713
  return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
2298
- /* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
2299
- /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
2714
+ /* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "Add Jira View" }),
2715
+ /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Tab to switch fields, Enter to save, Esc to cancel" }),
2300
2716
  /* @__PURE__ */ jsx8(Box8, { marginTop: 1 }),
2301
2717
  error && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "red", children: error }) }),
2302
2718
  /* @__PURE__ */ jsxs7(Box8, { children: [
2303
- /* @__PURE__ */ jsx8(Text7, { color: "blue", children: "Ticket: " }),
2304
- /* @__PURE__ */ jsx8(TextInput, { value: ticketInput, onChange: setTicketInput, placeholder: "PROJ-123", isActive: !loading })
2719
+ /* @__PURE__ */ jsx8(Text7, { color: "blue", children: "URL: " }),
2720
+ /* @__PURE__ */ jsx8(
2721
+ TextInput,
2722
+ {
2723
+ value: url,
2724
+ onChange: setUrl,
2725
+ placeholder: "https://company.atlassian.net/issues/?filter=12345",
2726
+ isActive: !loading && activeField === "url"
2727
+ }
2728
+ )
2729
+ ] }),
2730
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, children: [
2731
+ /* @__PURE__ */ jsx8(Text7, { color: "blue", children: "Name: " }),
2732
+ /* @__PURE__ */ jsx8(
2733
+ TextInput,
2734
+ {
2735
+ value: name,
2736
+ onChange: setName,
2737
+ placeholder: "(auto-generated from URL)",
2738
+ isActive: !loading && activeField === "name"
2739
+ }
2740
+ )
2305
2741
  ] }),
2306
- loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "Fetching ticket..." }) }),
2307
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
2742
+ loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "Validating view..." }) }),
2743
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Supports: filter URLs, JQL URLs, board URLs" }) })
2308
2744
  ] });
2309
2745
  }
2310
2746
 
2311
- // src/components/jira/JiraView.tsx
2747
+ // src/components/jira-browser/JiraSavedViewBrowserBox.tsx
2748
+ import open4 from "open";
2749
+ import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo2, useState as useState14 } from "react";
2312
2750
  import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
2313
- import { Box as Box12, Text as Text11, useInput as useInput9 } from "ink";
2751
+ import { Box as Box10, Text as Text9, useInput as useInput8 } from "ink";
2752
+ import { ScrollView as ScrollView6 } from "ink-scroll-view";
2753
+ import Spinner3 from "ink-spinner";
2314
2754
 
2315
- // src/components/jira/ChangeStatusModal.tsx
2316
- import { useEffect as useEffect9, useState as useState13 } from "react";
2755
+ // src/components/jira-browser/JiraIssueDetailView.tsx
2756
+ import open3 from "open";
2757
+ import { useEffect as useEffect9, useRef as useRef6, useState as useState13 } from "react";
2317
2758
  import { Box as Box9, Text as Text8, useInput as useInput7 } from "ink";
2759
+ import { ScrollView as ScrollView5 } from "ink-scroll-view";
2318
2760
  import SelectInput from "ink-select-input";
2761
+ import Spinner2 from "ink-spinner";
2319
2762
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2320
- function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
2321
- const [transitions, setTransitions] = useState13([]);
2763
+ function JiraIssueDetailView({
2764
+ issueKey,
2765
+ issueSummary,
2766
+ auth,
2767
+ myAccountId,
2768
+ myDisplayName,
2769
+ isActive,
2770
+ onClose,
2771
+ onIssueUpdated,
2772
+ onLogUpdated
2773
+ }) {
2774
+ var _a;
2775
+ const scrollRef = useRef6(null);
2776
+ const [detail, setDetail] = useState13(null);
2322
2777
  const [loading, setLoading] = useState13(true);
2323
- const [applying, setApplying] = useState13(false);
2324
2778
  const [error, setError] = useState13(null);
2779
+ const [mode, setMode] = useState13("normal");
2780
+ const [transitions, setTransitions] = useState13([]);
2781
+ const [transitionsLoading, setTransitionsLoading] = useState13(false);
2782
+ const [actionLoading, setActionLoading] = useState13(null);
2783
+ const [actionError, setActionError] = useState13(null);
2325
2784
  useEffect9(() => {
2326
- const fetchTransitions = async () => {
2327
- const siteUrl = getJiraSiteUrl(repoPath);
2328
- const creds = getJiraCredentials(repoPath);
2329
- if (!siteUrl || !creds.email || !creds.apiToken) {
2330
- setError("Jira not configured");
2331
- duckEvents.emit("error");
2332
- setLoading(false);
2333
- return;
2334
- }
2335
- const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
2336
- const result = await getTransitions(auth, ticketKey);
2785
+ setLoading(true);
2786
+ setError(null);
2787
+ getIssueDetail(auth, issueKey).then((result) => {
2337
2788
  if (result.success) {
2338
- setTransitions(result.data);
2789
+ setDetail(result.data);
2339
2790
  } else {
2340
2791
  setError(result.error);
2341
- duckEvents.emit("error");
2342
2792
  }
2343
2793
  setLoading(false);
2344
- };
2345
- fetchTransitions();
2346
- }, [repoPath, ticketKey]);
2347
- const handleSelect = async (item) => {
2348
- setApplying(true);
2349
- setError(null);
2350
- const siteUrl = getJiraSiteUrl(repoPath);
2351
- const creds = getJiraCredentials(repoPath);
2352
- if (!siteUrl || !creds.email || !creds.apiToken) {
2353
- setError("Jira not configured");
2354
- duckEvents.emit("error");
2355
- setApplying(false);
2356
- return;
2794
+ });
2795
+ }, [issueKey, auth.siteUrl]);
2796
+ const getIssueUrl = () => `${auth.siteUrl}/browse/${issueKey}`;
2797
+ const openTransitionPicker = async () => {
2798
+ setTransitionsLoading(true);
2799
+ setActionError(null);
2800
+ const result = await getTransitions(auth, issueKey);
2801
+ if (result.success) {
2802
+ setTransitions(result.data);
2803
+ setMode("transitions");
2804
+ } else {
2805
+ setActionError(result.error);
2357
2806
  }
2358
- const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
2359
- const result = await applyTransition(auth, ticketKey, item.value);
2807
+ setTransitionsLoading(false);
2808
+ };
2809
+ const handleTransitionSelect = async (item) => {
2810
+ setMode("normal");
2811
+ setActionLoading("Updating status...");
2812
+ setActionError(null);
2813
+ const result = await applyTransition(auth, issueKey, item.value);
2360
2814
  if (result.success) {
2361
2815
  const transition = transitions.find((t) => t.id === item.value);
2362
2816
  const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
2817
+ const oldStatus = (detail == null ? void 0 : detail.fields.status.name) ?? "Unknown";
2818
+ setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, status: { name: newStatus } } } : prev);
2819
+ onIssueUpdated(issueKey, { status: newStatus });
2363
2820
  duckEvents.emit("jira:transition");
2364
- onComplete(newStatus);
2821
+ logJiraStatusChanged(issueKey, issueSummary, oldStatus, newStatus);
2822
+ onLogUpdated == null ? void 0 : onLogUpdated();
2365
2823
  } else {
2366
- setError(result.error);
2824
+ setActionError(result.error);
2825
+ duckEvents.emit("error");
2826
+ }
2827
+ setActionLoading(null);
2828
+ };
2829
+ const handleAssignToMe = async () => {
2830
+ if (!myAccountId || !myDisplayName) return;
2831
+ setActionLoading("Assigning...");
2832
+ setActionError(null);
2833
+ const result = await assignIssue(auth, issueKey, myAccountId);
2834
+ if (result.success) {
2835
+ const assignee = { accountId: myAccountId, displayName: myDisplayName };
2836
+ setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee } } : prev);
2837
+ onIssueUpdated(issueKey, { assignee });
2838
+ duckEvents.emit("jira:assigned");
2839
+ logJiraAssigneeChanged(issueKey, issueSummary, "assigned", myDisplayName);
2840
+ onLogUpdated == null ? void 0 : onLogUpdated();
2841
+ } else {
2842
+ setActionError(result.error);
2367
2843
  duckEvents.emit("error");
2368
- setApplying(false);
2369
2844
  }
2845
+ setActionLoading(null);
2846
+ };
2847
+ const handleUnassign = async () => {
2848
+ setActionLoading("Unassigning...");
2849
+ setActionError(null);
2850
+ const result = await unassignIssue(auth, issueKey);
2851
+ if (result.success) {
2852
+ setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee: null } } : prev);
2853
+ onIssueUpdated(issueKey, { assignee: null });
2854
+ duckEvents.emit("jira:unassigned");
2855
+ logJiraAssigneeChanged(issueKey, issueSummary, "unassigned");
2856
+ onLogUpdated == null ? void 0 : onLogUpdated();
2857
+ } else {
2858
+ setActionError(result.error);
2859
+ duckEvents.emit("error");
2860
+ }
2861
+ setActionLoading(null);
2370
2862
  };
2371
2863
  useInput7(
2372
- (_input, key) => {
2373
- if (key.escape && !applying) {
2374
- onCancel();
2864
+ (input, key) => {
2865
+ var _a2, _b;
2866
+ if (mode === "transitions") {
2867
+ if (key.escape) {
2868
+ setMode("normal");
2869
+ }
2870
+ return;
2375
2871
  }
2376
- },
2377
- { isActive: !applying }
2378
- );
2379
- const items = transitions.map((t) => ({
2380
- label: t.name,
2381
- value: t.id
2382
- }));
2383
- const initialIndex = Math.max(
2384
- 0,
2385
- transitions.findIndex((t) => t.to.name === currentStatus)
2386
- );
2387
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
2388
- /* @__PURE__ */ jsxs8(Text8, { bold: true, color: "yellow", children: [
2389
- "Change Status: ",
2872
+ if (key.escape && !actionLoading) {
2873
+ onClose();
2874
+ return;
2875
+ }
2876
+ if (key.upArrow || input === "k") {
2877
+ (_a2 = scrollRef.current) == null ? void 0 : _a2.scrollBy(-1);
2878
+ }
2879
+ if (key.downArrow || input === "j") {
2880
+ (_b = scrollRef.current) == null ? void 0 : _b.scrollBy(1);
2881
+ }
2882
+ if (input === "o") {
2883
+ open3(getIssueUrl()).catch(() => {
2884
+ });
2885
+ }
2886
+ if (input === "y") {
2887
+ copyToClipboard(getIssueUrl());
2888
+ }
2889
+ if (input === "s" && !actionLoading) {
2890
+ openTransitionPicker();
2891
+ }
2892
+ if (input === "a" && !actionLoading && myAccountId) {
2893
+ handleAssignToMe();
2894
+ }
2895
+ if (input === "A" && !actionLoading) {
2896
+ handleUnassign();
2897
+ }
2898
+ },
2899
+ { isActive }
2900
+ );
2901
+ const statusColor = getStatusColor((detail == null ? void 0 : detail.fields.status.name) ?? "");
2902
+ const descriptionMd = (detail == null ? void 0 : detail.fields.description) ? adfToMarkdown(detail.fields.description) : null;
2903
+ const comments = (detail == null ? void 0 : detail.fields.comment.comments) ?? [];
2904
+ const totalComments = (detail == null ? void 0 : detail.fields.comment.total) ?? 0;
2905
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", flexGrow: 1, children: [
2906
+ /* @__PURE__ */ jsxs8(Box9, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
2907
+ loading && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
2908
+ /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
2909
+ " Loading issue details..."
2910
+ ] }) }),
2911
+ error && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "red", children: error }) }),
2912
+ !loading && !error && detail && /* @__PURE__ */ jsx9(ScrollView5, { ref: scrollRef, children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 1, children: [
2913
+ /* @__PURE__ */ jsxs8(Box9, { children: [
2914
+ /* @__PURE__ */ jsx9(Text8, { bold: true, color: "blue", children: detail.key }),
2915
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2916
+ " ",
2917
+ detail.fields.summary
2918
+ ] })
2919
+ ] }),
2920
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
2921
+ /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Status:" }),
2922
+ /* @__PURE__ */ jsx9(Text8, { color: statusColor, children: detail.fields.status.name }),
2923
+ /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Assignee:" }),
2924
+ /* @__PURE__ */ jsx9(Text8, { children: ((_a = detail.fields.assignee) == null ? void 0 : _a.displayName) ?? "Unassigned" })
2925
+ ] }),
2926
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
2927
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
2928
+ /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Description:" }),
2929
+ descriptionMd ? /* @__PURE__ */ jsx9(Markdown, { children: descriptionMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No description" })
2930
+ ] }),
2931
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Divider, {}) }),
2932
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
2933
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2934
+ "Comments (",
2935
+ totalComments,
2936
+ "):"
2937
+ ] }),
2938
+ comments.length === 0 && /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "No comments" }),
2939
+ comments.map((comment) => /* @__PURE__ */ jsx9(CommentBlock, { comment }, comment.id)),
2940
+ comments.length < totalComments && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2941
+ "Showing ",
2942
+ comments.length,
2943
+ " of ",
2944
+ totalComments,
2945
+ " comments. Open in browser to see all."
2946
+ ] }) })
2947
+ ] })
2948
+ ] }) })
2949
+ ] }),
2950
+ mode === "transitions" && /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2951
+ /* @__PURE__ */ jsx9(Text8, { bold: true, color: "yellow", children: "Change Status" }),
2952
+ /* @__PURE__ */ jsx9(
2953
+ SelectInput,
2954
+ {
2955
+ items: transitions.map((t) => ({ label: t.name, value: t.id })),
2956
+ onSelect: handleTransitionSelect
2957
+ }
2958
+ ),
2959
+ /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc to cancel" })
2960
+ ] }),
2961
+ /* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
2962
+ actionLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
2963
+ /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
2964
+ " ",
2965
+ actionLoading
2966
+ ] }),
2967
+ actionError && /* @__PURE__ */ jsx9(Text8, { color: "red", children: actionError }),
2968
+ transitionsLoading && /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
2969
+ /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }),
2970
+ " Loading transitions..."
2971
+ ] }),
2972
+ !actionLoading && !transitionsLoading && mode === "normal" && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc close \xB7 j/k scroll \xB7 s status \xB7 a assign \xB7 A unassign \xB7 o open \xB7 y copy" })
2973
+ ] })
2974
+ ] });
2975
+ }
2976
+ function CommentBlock({ comment }) {
2977
+ const bodyMd = adfToMarkdown(comment.body);
2978
+ return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
2979
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
2980
+ /* @__PURE__ */ jsx9(Text8, { bold: true, children: comment.author.displayName }),
2981
+ /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: timeAgo(comment.created) })
2982
+ ] }),
2983
+ bodyMd ? /* @__PURE__ */ jsx9(Markdown, { children: bodyMd }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, italic: true, children: "Empty comment" })
2984
+ ] });
2985
+ }
2986
+ function getStatusColor(status) {
2987
+ const lower = status.toLowerCase();
2988
+ if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
2989
+ if (lower === "in progress" || lower === "in review") return "yellow";
2990
+ return "gray";
2991
+ }
2992
+
2993
+ // src/components/jira-browser/JiraSavedViewBrowserBox.tsx
2994
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2995
+ function groupBySprint(issues) {
2996
+ const groups = /* @__PURE__ */ new Map();
2997
+ for (const issue of issues) {
2998
+ const sprint = issue.fields.sprint ?? null;
2999
+ const key = sprint ? String(sprint.id) : "__backlog__";
3000
+ if (!groups.has(key)) {
3001
+ groups.set(key, { sprint, issues: [] });
3002
+ }
3003
+ groups.get(key).issues.push(issue);
3004
+ }
3005
+ const stateOrder = { active: 0, future: 1, closed: 2 };
3006
+ const entries = [...groups.values()];
3007
+ entries.sort((a, b) => {
3008
+ if (!a.sprint) return 1;
3009
+ if (!b.sprint) return -1;
3010
+ const aOrder = stateOrder[a.sprint.state] ?? 3;
3011
+ const bOrder = stateOrder[b.sprint.state] ?? 3;
3012
+ return aOrder - bOrder;
3013
+ });
3014
+ return entries;
3015
+ }
3016
+ function buildRows(groups) {
3017
+ var _a;
3018
+ const hasSprints = groups.some((g) => g.sprint !== null);
3019
+ if (!hasSprints) {
3020
+ return groups.flatMap((g) => g.issues.map((issue) => ({ type: "issue", issue })));
3021
+ }
3022
+ const rows = [];
3023
+ for (const group of groups) {
3024
+ const label = group.sprint ? group.sprint.name : "Backlog";
3025
+ const state = ((_a = group.sprint) == null ? void 0 : _a.state) ?? null;
3026
+ rows.push({ type: "header", label, state });
3027
+ for (const issue of group.issues) {
3028
+ rows.push({ type: "issue", issue });
3029
+ }
3030
+ }
3031
+ return rows;
3032
+ }
3033
+ function JiraSavedViewBrowserBox({
3034
+ view,
3035
+ auth,
3036
+ myAccountId,
3037
+ myDisplayName,
3038
+ isActive,
3039
+ onInputModeChange,
3040
+ onLogUpdated
3041
+ }) {
3042
+ const [issues, setIssues] = useState14([]);
3043
+ const [loading, setLoading] = useState14(false);
3044
+ const [error, setError] = useState14(null);
3045
+ const [total, setTotal] = useState14(0);
3046
+ const [highlightedIndex, setHighlightedIndex] = useState14(0);
3047
+ const [inputText, setInputText] = useState14("");
3048
+ const [searchText, setSearchText] = useState14("");
3049
+ const [isFiltering, setIsFiltering] = useState14(false);
3050
+ const [assigneeFilter, setAssigneeFilter] = useState14("all");
3051
+ const [detailIssue, setDetailIssue] = useState14(null);
3052
+ useEffect10(() => {
3053
+ onInputModeChange == null ? void 0 : onInputModeChange(isFiltering || detailIssue !== null);
3054
+ }, [isFiltering, detailIssue, onInputModeChange]);
3055
+ const title = "[6] Issues";
3056
+ const borderColor = isActive ? "yellow" : void 0;
3057
+ const displayTitle = view ? `${title} - ${view.name}` : title;
3058
+ const filteredIssues = useMemo2(() => {
3059
+ if (assigneeFilter === "unassigned") {
3060
+ return issues.filter((issue) => !issue.fields.assignee);
3061
+ }
3062
+ if (assigneeFilter === "me" && myAccountId) {
3063
+ return issues.filter((issue) => {
3064
+ var _a;
3065
+ return ((_a = issue.fields.assignee) == null ? void 0 : _a.accountId) === myAccountId;
3066
+ });
3067
+ }
3068
+ return issues;
3069
+ }, [issues, assigneeFilter, myAccountId]);
3070
+ const rows = useMemo2(() => {
3071
+ const groups = groupBySprint(filteredIssues);
3072
+ return buildRows(groups);
3073
+ }, [filteredIssues]);
3074
+ const navigableIndices = useMemo2(
3075
+ () => rows.map((r, i) => r.type === "issue" ? i : -1).filter((i) => i >= 0),
3076
+ [rows]
3077
+ );
3078
+ const scrollRef = useScrollToIndex(navigableIndices.length > 0 ? navigableIndices[highlightedIndex] ?? 0 : 0);
3079
+ const currentIssue = useMemo2(() => {
3080
+ if (navigableIndices.length === 0) return null;
3081
+ const rowIdx = navigableIndices[highlightedIndex];
3082
+ if (rowIdx === void 0) return null;
3083
+ const row = rows[rowIdx];
3084
+ return (row == null ? void 0 : row.type) === "issue" ? row.issue : null;
3085
+ }, [rows, navigableIndices, highlightedIndex]);
3086
+ const hasMore = issues.length < total;
3087
+ const doFetch = useCallback9(
3088
+ async (search, startAt = 0, append = false) => {
3089
+ if (!view || !auth) return;
3090
+ setLoading(true);
3091
+ setError(null);
3092
+ const result = await fetchViewIssues(auth, view, {
3093
+ startAt,
3094
+ maxResults: 50,
3095
+ searchText: search || void 0
3096
+ });
3097
+ if (result.success) {
3098
+ setIssues((prev) => append ? [...prev, ...result.data.issues] : result.data.issues);
3099
+ setTotal(result.data.total);
3100
+ if (!append) setHighlightedIndex(0);
3101
+ } else {
3102
+ setError(result.error);
3103
+ if (!append) {
3104
+ setIssues([]);
3105
+ setTotal(0);
3106
+ }
3107
+ }
3108
+ setLoading(false);
3109
+ },
3110
+ [view, auth]
3111
+ );
3112
+ useEffect10(() => {
3113
+ if (view && auth) {
3114
+ setSearchText("");
3115
+ setInputText("");
3116
+ doFetch("");
3117
+ } else {
3118
+ setIssues([]);
3119
+ setTotal(0);
3120
+ setError(null);
3121
+ }
3122
+ }, [view == null ? void 0 : view.id, auth == null ? void 0 : auth.siteUrl]);
3123
+ const getIssueUrl = (issue) => {
3124
+ if (!auth) return null;
3125
+ return `${auth.siteUrl}/browse/${issue.key}`;
3126
+ };
3127
+ const handleIssueUpdated = useCallback9(
3128
+ (key, updates) => {
3129
+ setIssues(
3130
+ (prev) => prev.map((issue) => {
3131
+ if (issue.key !== key) return issue;
3132
+ const updated = { ...issue, fields: { ...issue.fields } };
3133
+ if (updates.status !== void 0) {
3134
+ updated.fields.status = { name: updates.status };
3135
+ }
3136
+ if ("assignee" in updates) {
3137
+ updated.fields.assignee = updates.assignee;
3138
+ }
3139
+ return updated;
3140
+ })
3141
+ );
3142
+ },
3143
+ []
3144
+ );
3145
+ const hasActiveFilters = searchText.length > 0 || assigneeFilter !== "all";
3146
+ useInput8(
3147
+ (input, key) => {
3148
+ if (detailIssue) return;
3149
+ if (isFiltering) {
3150
+ if (key.escape) {
3151
+ setIsFiltering(false);
3152
+ setInputText(searchText);
3153
+ return;
3154
+ }
3155
+ if (key.return) {
3156
+ setIsFiltering(false);
3157
+ const newSearch = inputText.trim();
3158
+ if (newSearch !== searchText) {
3159
+ setSearchText(newSearch);
3160
+ doFetch(newSearch);
3161
+ }
3162
+ return;
3163
+ }
3164
+ if (key.backspace || key.delete) {
3165
+ setInputText((prev) => prev.slice(0, -1));
3166
+ return;
3167
+ }
3168
+ if (input && input.length > 0) {
3169
+ const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
3170
+ if (printable.length > 0) {
3171
+ setInputText((prev) => prev + printable);
3172
+ }
3173
+ return;
3174
+ }
3175
+ return;
3176
+ }
3177
+ if (navigableIndices.length > 0) {
3178
+ if (key.upArrow || input === "k") {
3179
+ setHighlightedIndex((i) => Math.max(0, i - 1));
3180
+ }
3181
+ if (key.downArrow || input === "j") {
3182
+ setHighlightedIndex((i) => Math.min(navigableIndices.length - 1, i + 1));
3183
+ }
3184
+ if (input === "o" && currentIssue) {
3185
+ const url = getIssueUrl(currentIssue);
3186
+ if (url) open4(url).catch(() => {
3187
+ });
3188
+ }
3189
+ if (input === "y" && currentIssue) {
3190
+ const url = getIssueUrl(currentIssue);
3191
+ if (url) copyToClipboard(url);
3192
+ }
3193
+ if (key.return && currentIssue && auth) {
3194
+ setDetailIssue({ key: currentIssue.key, summary: currentIssue.fields.summary });
3195
+ }
3196
+ }
3197
+ if (input === "/") {
3198
+ setIsFiltering(true);
3199
+ setInputText(searchText);
3200
+ return;
3201
+ }
3202
+ if (input === "u") {
3203
+ setAssigneeFilter((f) => f === "unassigned" ? "all" : "unassigned");
3204
+ setHighlightedIndex(0);
3205
+ return;
3206
+ }
3207
+ if (input === "m") {
3208
+ setAssigneeFilter((f) => f === "me" ? "all" : "me");
3209
+ setHighlightedIndex(0);
3210
+ return;
3211
+ }
3212
+ if (input === "x") {
3213
+ setAssigneeFilter("all");
3214
+ if (searchText) {
3215
+ setSearchText("");
3216
+ setInputText("");
3217
+ doFetch("");
3218
+ }
3219
+ setHighlightedIndex(0);
3220
+ return;
3221
+ }
3222
+ if (input === "l" && hasMore) {
3223
+ doFetch(searchText, issues.length, true);
3224
+ return;
3225
+ }
3226
+ if (input === "r") {
3227
+ doFetch(searchText);
3228
+ }
3229
+ },
3230
+ { isActive }
3231
+ );
3232
+ const filterParts = [];
3233
+ if (assigneeFilter === "unassigned") filterParts.push("unassigned");
3234
+ if (assigneeFilter === "me") filterParts.push("mine");
3235
+ if (searchText) filterParts.push(`"${searchText}"`);
3236
+ return /* @__PURE__ */ jsx10(TitledBox4, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, children: detailIssue && auth ? /* @__PURE__ */ jsx10(
3237
+ JiraIssueDetailView,
3238
+ {
3239
+ issueKey: detailIssue.key,
3240
+ issueSummary: detailIssue.summary,
3241
+ auth,
3242
+ myAccountId,
3243
+ myDisplayName,
3244
+ isActive,
3245
+ onClose: () => setDetailIssue(null),
3246
+ onIssueUpdated: handleIssueUpdated,
3247
+ onLogUpdated
3248
+ }
3249
+ ) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
3250
+ (isFiltering || hasActiveFilters) && /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
3251
+ /* @__PURE__ */ jsx10(Text9, { color: "blue", children: "Search: " }),
3252
+ isFiltering ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
3253
+ /* @__PURE__ */ jsx10(Text9, { children: inputText }),
3254
+ /* @__PURE__ */ jsx10(Text9, { backgroundColor: "yellow", children: " " })
3255
+ ] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
3256
+ /* @__PURE__ */ jsx10(Text9, { children: filterParts.join(" + ") }),
3257
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3258
+ " ",
3259
+ "(",
3260
+ filteredIssues.length,
3261
+ "/",
3262
+ total,
3263
+ ")"
3264
+ ] })
3265
+ ] })
3266
+ ] }),
3267
+ /* @__PURE__ */ jsxs9(Box10, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
3268
+ !view && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Select a view to browse issues" }) }),
3269
+ view && loading && issues.length === 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "yellow", children: [
3270
+ /* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
3271
+ " Loading issues..."
3272
+ ] }) }),
3273
+ view && error && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
3274
+ view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: searchText ? "No issues match search" : "No issues found" }) }),
3275
+ view && !loading && !error && filteredIssues.length === 0 && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "No issues match filter" }) }),
3276
+ rows.length > 0 && /* @__PURE__ */ jsx10(ScrollView6, { ref: scrollRef, children: rows.map((row, rowIdx) => {
3277
+ if (row.type === "header") {
3278
+ const stateLabel = row.state === "active" ? " (active)" : "";
3279
+ return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, marginTop: rowIdx > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "magenta", children: [
3280
+ row.label,
3281
+ stateLabel && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: stateLabel })
3282
+ ] }) }, `header-${row.label}`);
3283
+ }
3284
+ const navIdx = navigableIndices.indexOf(rowIdx);
3285
+ const isHighlighted = navIdx === highlightedIndex;
3286
+ const cursor = isHighlighted ? ">" : " ";
3287
+ const statusColor = getStatusColor2(row.issue.fields.status.name);
3288
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
3289
+ /* @__PURE__ */ jsxs9(Text9, { color: isHighlighted ? "yellow" : void 0, children: [
3290
+ cursor,
3291
+ " "
3292
+ ] }),
3293
+ /* @__PURE__ */ jsx10(Text9, { bold: true, color: "blue", children: row.issue.key }),
3294
+ /* @__PURE__ */ jsxs9(Text9, { children: [
3295
+ " ",
3296
+ row.issue.fields.summary
3297
+ ] }),
3298
+ /* @__PURE__ */ jsxs9(Text9, { color: statusColor, children: [
3299
+ " [",
3300
+ row.issue.fields.status.name,
3301
+ "]"
3302
+ ] })
3303
+ ] }, row.issue.key);
3304
+ }) })
3305
+ ] }),
3306
+ view && !loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3307
+ issues.length,
3308
+ " of ",
3309
+ total,
3310
+ " loaded",
3311
+ hasMore && " \xB7 l to load more"
3312
+ ] }) }),
3313
+ view && loading && issues.length > 0 && /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Loading more..." }) })
3314
+ ] }) }) });
3315
+ }
3316
+ function getStatusColor2(status) {
3317
+ const lower = status.toLowerCase();
3318
+ if (lower === "done" || lower === "closed" || lower === "resolved") return "green";
3319
+ if (lower === "in progress" || lower === "in review") return "yellow";
3320
+ return "gray";
3321
+ }
3322
+
3323
+ // src/components/jira-browser/JiraSavedViewsBox.tsx
3324
+ import { useEffect as useEffect11, useState as useState15 } from "react";
3325
+ import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
3326
+ import { Box as Box11, Text as Text10, useInput as useInput9 } from "ink";
3327
+ import { ScrollView as ScrollView7 } from "ink-scroll-view";
3328
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3329
+ function JiraSavedViewsBox({
3330
+ views,
3331
+ selectedViewId,
3332
+ highlightedIndex,
3333
+ onHighlight,
3334
+ onSelect,
3335
+ onAdd,
3336
+ onDelete,
3337
+ onRename,
3338
+ isActive,
3339
+ onInputModeChange
3340
+ }) {
3341
+ const scrollRef = useScrollToIndex(highlightedIndex);
3342
+ const [renaming, setRenaming] = useState15(null);
3343
+ const [renameValue, setRenameValue] = useState15("");
3344
+ useEffect11(() => {
3345
+ onInputModeChange == null ? void 0 : onInputModeChange(renaming !== null);
3346
+ }, [renaming, onInputModeChange]);
3347
+ const title = "[5] Views";
3348
+ const borderColor = isActive ? "yellow" : void 0;
3349
+ useInput9(
3350
+ (input, key) => {
3351
+ if (renaming) {
3352
+ if (key.escape) {
3353
+ setRenaming(null);
3354
+ setRenameValue("");
3355
+ return;
3356
+ }
3357
+ if (key.return) {
3358
+ const trimmed = renameValue.trim();
3359
+ if (trimmed.length > 0) {
3360
+ onRename(renaming, trimmed);
3361
+ }
3362
+ setRenaming(null);
3363
+ setRenameValue("");
3364
+ return;
3365
+ }
3366
+ return;
3367
+ }
3368
+ if (views.length === 0) {
3369
+ if (input === "a") onAdd();
3370
+ return;
3371
+ }
3372
+ if (key.upArrow || input === "k") {
3373
+ onHighlight(Math.max(0, highlightedIndex - 1));
3374
+ }
3375
+ if (key.downArrow || input === "j") {
3376
+ onHighlight(Math.min(views.length - 1, highlightedIndex + 1));
3377
+ }
3378
+ if (input === " ") {
3379
+ const view = views[highlightedIndex];
3380
+ if (view) onSelect(view.id);
3381
+ }
3382
+ if (input === "a") onAdd();
3383
+ if (input === "e") {
3384
+ const view = views[highlightedIndex];
3385
+ if (view) {
3386
+ setRenaming(view.id);
3387
+ setRenameValue(view.name);
3388
+ }
3389
+ }
3390
+ if (input === "d") {
3391
+ const view = views[highlightedIndex];
3392
+ if (view) onDelete(view.id);
3393
+ }
3394
+ },
3395
+ { isActive }
3396
+ );
3397
+ return /* @__PURE__ */ jsx11(TitledBox5, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
3398
+ views.length === 0 && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "No saved views" }),
3399
+ views.length > 0 && /* @__PURE__ */ jsx11(ScrollView7, { ref: scrollRef, children: views.map((view, idx) => {
3400
+ const isHighlighted = isActive && idx === highlightedIndex;
3401
+ const isSelected = view.id === selectedViewId;
3402
+ const isRenaming = view.id === renaming;
3403
+ const cursor = isHighlighted ? ">" : " ";
3404
+ const nameColor = isSelected ? "green" : void 0;
3405
+ const indicator = isSelected ? " *" : "";
3406
+ return /* @__PURE__ */ jsxs10(Box11, { children: [
3407
+ /* @__PURE__ */ jsxs10(Text10, { color: isHighlighted ? "yellow" : void 0, children: [
3408
+ cursor,
3409
+ " "
3410
+ ] }),
3411
+ isRenaming ? /* @__PURE__ */ jsx11(TextInput, { value: renameValue, onChange: setRenameValue, isActive: true }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
3412
+ /* @__PURE__ */ jsx11(Text10, { color: nameColor, children: view.name }),
3413
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: indicator })
3414
+ ] })
3415
+ ] }, view.id);
3416
+ }) })
3417
+ ] }) });
3418
+ }
3419
+
3420
+ // src/components/jira-browser/JiraBrowserView.tsx
3421
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3422
+ function JiraBrowserView({
3423
+ isActive,
3424
+ focusedBox,
3425
+ onFocusedBoxChange,
3426
+ onModalChange,
3427
+ onLogUpdated
3428
+ }) {
3429
+ const repo = useGitRepo();
3430
+ const modal = useModal();
3431
+ const [views, setViews] = useState16([]);
3432
+ const [selectedViewId, setSelectedViewId] = useState16(null);
3433
+ const [highlightedIndex, setHighlightedIndex] = useState16(0);
3434
+ const [addError, setAddError] = useState16(void 0);
3435
+ const [addLoading, setAddLoading] = useState16(false);
3436
+ const [myAccountId, setMyAccountId] = useState16(null);
3437
+ const [myDisplayName, setMyDisplayName] = useState16(null);
3438
+ const [inputModeActive, setInputModeActive] = useState16(false);
3439
+ const lastRepoRef = useRef7(null);
3440
+ const auth = useMemo3(() => {
3441
+ if (!repo.repoPath || !isJiraConfigured(repo.repoPath)) return null;
3442
+ const siteUrl = getJiraSiteUrl(repo.repoPath);
3443
+ const creds = getJiraCredentials(repo.repoPath);
3444
+ if (!siteUrl || !creds.email || !creds.apiToken) return null;
3445
+ return { siteUrl, email: creds.email, apiToken: creds.apiToken };
3446
+ }, [repo.repoPath]);
3447
+ const selectedView = useMemo3(() => views.find((v) => v.id === selectedViewId) ?? null, [views, selectedViewId]);
3448
+ useEffect12(() => {
3449
+ if (!repo.repoPath || repo.repoPath === lastRepoRef.current) return;
3450
+ lastRepoRef.current = repo.repoPath;
3451
+ const loaded = getSavedViews(repo.repoPath);
3452
+ setViews(loaded);
3453
+ if (loaded.length > 0 && !selectedViewId) {
3454
+ setSelectedViewId(loaded[0].id);
3455
+ }
3456
+ }, [repo.repoPath]);
3457
+ useEffect12(() => {
3458
+ if (!auth) {
3459
+ setMyAccountId(null);
3460
+ return;
3461
+ }
3462
+ getCurrentUser(auth).then((result) => {
3463
+ if (result.success) {
3464
+ setMyAccountId(result.data.accountId);
3465
+ setMyDisplayName(result.data.displayName);
3466
+ }
3467
+ });
3468
+ }, [auth == null ? void 0 : auth.siteUrl, auth == null ? void 0 : auth.email]);
3469
+ useEffect12(() => {
3470
+ onModalChange == null ? void 0 : onModalChange(modal.isOpen || inputModeActive);
3471
+ }, [modal.isOpen, inputModeActive, onModalChange]);
3472
+ useEffect12(() => {
3473
+ if (!isActive) modal.close();
3474
+ }, [isActive, modal.close]);
3475
+ const refreshViews = useCallback10(() => {
3476
+ if (!repo.repoPath) return;
3477
+ setViews(getSavedViews(repo.repoPath));
3478
+ }, [repo.repoPath]);
3479
+ const handleSelectView = useCallback10((viewId) => {
3480
+ setSelectedViewId(viewId);
3481
+ }, []);
3482
+ const handleAddView = useCallback10(
3483
+ async (url, name) => {
3484
+ if (!repo.repoPath) return;
3485
+ const source = parseJiraUrl(url);
3486
+ if (!source) {
3487
+ setAddError("Unrecognized Jira URL format");
3488
+ return;
3489
+ }
3490
+ setAddLoading(true);
3491
+ setAddError(void 0);
3492
+ try {
3493
+ const view = addSavedView(repo.repoPath, name, url, source);
3494
+ refreshViews();
3495
+ setSelectedViewId(view.id);
3496
+ modal.close();
3497
+ } catch {
3498
+ setAddError("Failed to save view");
3499
+ } finally {
3500
+ setAddLoading(false);
3501
+ }
3502
+ },
3503
+ [repo.repoPath, refreshViews, modal.close]
3504
+ );
3505
+ const handleRenameView = useCallback10(
3506
+ (viewId, newName) => {
3507
+ if (!repo.repoPath) return;
3508
+ renameSavedView(repo.repoPath, viewId, newName);
3509
+ refreshViews();
3510
+ },
3511
+ [repo.repoPath, refreshViews]
3512
+ );
3513
+ const handleDeleteView = useCallback10(
3514
+ (viewId) => {
3515
+ if (!repo.repoPath) return;
3516
+ removeSavedView(repo.repoPath, viewId);
3517
+ refreshViews();
3518
+ if (selectedViewId === viewId) {
3519
+ const remaining = getSavedViews(repo.repoPath);
3520
+ setSelectedViewId(remaining.length > 0 ? remaining[0].id : null);
3521
+ }
3522
+ setHighlightedIndex((i) => Math.max(0, i - 1));
3523
+ },
3524
+ [repo.repoPath, selectedViewId, refreshViews]
3525
+ );
3526
+ useInput10(
3527
+ (input) => {
3528
+ if (input === "5") onFocusedBoxChange("saved-views");
3529
+ if (input === "6") onFocusedBoxChange("browser");
3530
+ },
3531
+ { isActive: isActive && !modal.isOpen }
3532
+ );
3533
+ if (modal.type === "add") {
3534
+ return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(
3535
+ AddViewModal,
3536
+ {
3537
+ onSubmit: handleAddView,
3538
+ onCancel: () => {
3539
+ modal.close();
3540
+ setAddError(void 0);
3541
+ },
3542
+ loading: addLoading,
3543
+ error: addError
3544
+ }
3545
+ ) });
3546
+ }
3547
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", flexGrow: 1, children: [
3548
+ /* @__PURE__ */ jsx12(
3549
+ JiraSavedViewsBox,
3550
+ {
3551
+ views,
3552
+ selectedViewId,
3553
+ highlightedIndex,
3554
+ onHighlight: setHighlightedIndex,
3555
+ onSelect: handleSelectView,
3556
+ onAdd: () => modal.open("add"),
3557
+ onDelete: handleDeleteView,
3558
+ onRename: handleRenameView,
3559
+ isActive: isActive && focusedBox === "saved-views",
3560
+ onInputModeChange: setInputModeActive
3561
+ }
3562
+ ),
3563
+ /* @__PURE__ */ jsx12(
3564
+ JiraSavedViewBrowserBox,
3565
+ {
3566
+ view: selectedView,
3567
+ auth,
3568
+ myAccountId,
3569
+ myDisplayName,
3570
+ isActive: isActive && focusedBox === "browser",
3571
+ onInputModeChange: setInputModeActive,
3572
+ onLogUpdated
3573
+ }
3574
+ )
3575
+ ] });
3576
+ }
3577
+
3578
+ // src/components/jira/JiraView.tsx
3579
+ import open5 from "open";
3580
+ import { useEffect as useEffect14, useRef as useRef8 } from "react";
3581
+
3582
+ // src/components/jira/LinkTicketModal.tsx
3583
+ import { useState as useState17 } from "react";
3584
+ import { Box as Box13, Text as Text11, useInput as useInput11 } from "ink";
3585
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3586
+ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
3587
+ const [ticketInput, setTicketInput] = useState17("");
3588
+ const canSubmit = ticketInput.trim().length > 0;
3589
+ useInput11(
3590
+ (_input, key) => {
3591
+ if (loading) return;
3592
+ if (key.escape) {
3593
+ onCancel();
3594
+ return;
3595
+ }
3596
+ if (key.return && canSubmit) {
3597
+ onSubmit(ticketInput.trim());
3598
+ }
3599
+ },
3600
+ { isActive: !loading }
3601
+ );
3602
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
3603
+ /* @__PURE__ */ jsx13(Text11, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
3604
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Type ticket ID, Enter to submit, Esc to cancel" }),
3605
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1 }),
3606
+ error && /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "red", children: error }) }),
3607
+ /* @__PURE__ */ jsxs12(Box13, { children: [
3608
+ /* @__PURE__ */ jsx13(Text11, { color: "blue", children: "Ticket: " }),
3609
+ /* @__PURE__ */ jsx13(TextInput, { value: ticketInput, onChange: setTicketInput, placeholder: "PROJ-123", isActive: !loading })
3610
+ ] }),
3611
+ loading && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "yellow", children: "Fetching ticket..." }) }),
3612
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Examples: PROJ-123 or https://company.atlassian.net/browse/PROJ-123" }) })
3613
+ ] });
3614
+ }
3615
+
3616
+ // src/components/jira/JiraView.tsx
3617
+ import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
3618
+ import { Box as Box17, Text as Text15, useInput as useInput14 } from "ink";
3619
+
3620
+ // src/components/jira/ChangeStatusModal.tsx
3621
+ import { useEffect as useEffect13, useState as useState18 } from "react";
3622
+ import { Box as Box14, Text as Text12, useInput as useInput12 } from "ink";
3623
+ import SelectInput2 from "ink-select-input";
3624
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3625
+ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
3626
+ const [transitions, setTransitions] = useState18([]);
3627
+ const [loading, setLoading] = useState18(true);
3628
+ const [applying, setApplying] = useState18(false);
3629
+ const [error, setError] = useState18(null);
3630
+ useEffect13(() => {
3631
+ const fetchTransitions = async () => {
3632
+ const siteUrl = getJiraSiteUrl(repoPath);
3633
+ const creds = getJiraCredentials(repoPath);
3634
+ if (!siteUrl || !creds.email || !creds.apiToken) {
3635
+ setError("Jira not configured");
3636
+ duckEvents.emit("error");
3637
+ setLoading(false);
3638
+ return;
3639
+ }
3640
+ const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
3641
+ const result = await getTransitions(auth, ticketKey);
3642
+ if (result.success) {
3643
+ setTransitions(result.data);
3644
+ } else {
3645
+ setError(result.error);
3646
+ duckEvents.emit("error");
3647
+ }
3648
+ setLoading(false);
3649
+ };
3650
+ fetchTransitions();
3651
+ }, [repoPath, ticketKey]);
3652
+ const handleSelect = async (item) => {
3653
+ setApplying(true);
3654
+ setError(null);
3655
+ const siteUrl = getJiraSiteUrl(repoPath);
3656
+ const creds = getJiraCredentials(repoPath);
3657
+ if (!siteUrl || !creds.email || !creds.apiToken) {
3658
+ setError("Jira not configured");
3659
+ duckEvents.emit("error");
3660
+ setApplying(false);
3661
+ return;
3662
+ }
3663
+ const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
3664
+ const result = await applyTransition(auth, ticketKey, item.value);
3665
+ if (result.success) {
3666
+ const transition = transitions.find((t) => t.id === item.value);
3667
+ const newStatus = (transition == null ? void 0 : transition.to.name) ?? item.label;
3668
+ duckEvents.emit("jira:transition");
3669
+ onComplete(newStatus);
3670
+ } else {
3671
+ setError(result.error);
3672
+ duckEvents.emit("error");
3673
+ setApplying(false);
3674
+ }
3675
+ };
3676
+ useInput12(
3677
+ (_input, key) => {
3678
+ if (key.escape && !applying) {
3679
+ onCancel();
3680
+ }
3681
+ },
3682
+ { isActive: !applying }
3683
+ );
3684
+ const items = transitions.map((t) => ({
3685
+ label: t.name,
3686
+ value: t.id
3687
+ }));
3688
+ const initialIndex = Math.max(
3689
+ 0,
3690
+ transitions.findIndex((t) => t.to.name === currentStatus)
3691
+ );
3692
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
3693
+ /* @__PURE__ */ jsxs13(Text12, { bold: true, color: "yellow", children: [
3694
+ "Change Status: ",
2390
3695
  ticketKey
2391
3696
  ] }),
2392
- loading && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Loading transitions..." }),
2393
- error && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "red", children: error }) }),
2394
- !loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "No available transitions" }),
2395
- !loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx9(SelectInput, { items, initialIndex, onSelect: handleSelect }) }),
2396
- applying && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "yellow", children: "Updating status..." }) }),
2397
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Esc to cancel" }) })
3697
+ loading && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading transitions..." }),
3698
+ error && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "red", children: error }) }),
3699
+ !loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No available transitions" }),
3700
+ !loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx14(SelectInput2, { items, initialIndex, onSelect: handleSelect }) }),
3701
+ applying && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "yellow", children: "Updating status..." }) }),
3702
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Esc to cancel" }) })
2398
3703
  ] });
2399
3704
  }
2400
3705
 
2401
3706
  // src/components/jira/ConfigureJiraSiteModal.tsx
2402
- import { useState as useState14 } from "react";
2403
- import { Box as Box10, Text as Text9, useInput as useInput8 } from "ink";
2404
- import { ScrollView as ScrollView5 } from "ink-scroll-view";
3707
+ import { useState as useState19 } from "react";
3708
+ import { Box as Box15, Text as Text13, useInput as useInput13 } from "ink";
3709
+ import { ScrollView as ScrollView8 } from "ink-scroll-view";
2405
3710
 
2406
3711
  // src/lib/editor.ts
2407
3712
  import { spawnSync as spawnSync2 } from "child_process";
@@ -2432,7 +3737,7 @@ function openInEditor(content, filename) {
2432
3737
  }
2433
3738
 
2434
3739
  // src/components/jira/ConfigureJiraSiteModal.tsx
2435
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3740
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2436
3741
  var MAX_VISIBLE_ITEMS = 4;
2437
3742
  function ConfigureJiraSiteModal({
2438
3743
  initialSiteUrl,
@@ -2444,17 +3749,17 @@ function ConfigureJiraSiteModal({
2444
3749
  error
2445
3750
  }) {
2446
3751
  const hasExisting = existingConfigs.length > 0;
2447
- const [mode, setMode] = useState14(hasExisting ? "choose" : "manual");
2448
- const [selectedExisting, setSelectedExisting] = useState14(0);
3752
+ const [mode, setMode] = useState19(hasExisting ? "choose" : "manual");
3753
+ const [selectedExisting, setSelectedExisting] = useState19(0);
2449
3754
  const scrollRef = useScrollToIndex(selectedExisting);
2450
- const [siteUrl, setSiteUrl] = useState14(initialSiteUrl ?? "");
2451
- const [email, setEmail] = useState14(initialEmail ?? "");
2452
- const [apiToken, setApiToken] = useState14("");
2453
- const [selectedItem, setSelectedItem] = useState14("siteUrl");
3755
+ const [siteUrl, setSiteUrl] = useState19(initialSiteUrl ?? "");
3756
+ const [email, setEmail] = useState19(initialEmail ?? "");
3757
+ const [apiToken, setApiToken] = useState19("");
3758
+ const [selectedItem, setSelectedItem] = useState19("siteUrl");
2454
3759
  const items = ["siteUrl", "email", "apiToken", "submit"];
2455
3760
  const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
2456
3761
  const chooseItems = existingConfigs.length + 1;
2457
- useInput8(
3762
+ useInput13(
2458
3763
  (input, key) => {
2459
3764
  if (loading) return;
2460
3765
  if (key.escape) {
@@ -2526,38 +3831,38 @@ function ConfigureJiraSiteModal({
2526
3831
  const prefix = isSelected ? "> " : " ";
2527
3832
  const color = isSelected ? "yellow" : void 0;
2528
3833
  const displayValue = isSensitive && value ? "*".repeat(Math.min(value.length, 20)) : value;
2529
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
2530
- /* @__PURE__ */ jsxs9(Text9, { color, bold: isSelected, children: [
3834
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
3835
+ /* @__PURE__ */ jsxs14(Text13, { color, bold: isSelected, children: [
2531
3836
  prefix,
2532
3837
  label
2533
3838
  ] }),
2534
- value !== void 0 && /* @__PURE__ */ jsx10(Box10, { marginLeft: 4, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
3839
+ value !== void 0 && /* @__PURE__ */ jsx15(Box15, { marginLeft: 4, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
2535
3840
  ] });
2536
3841
  };
2537
3842
  if (mode === "choose") {
2538
3843
  const totalItems = existingConfigs.length + 1;
2539
3844
  const listHeight = Math.min(totalItems * 2, MAX_VISIBLE_ITEMS * 2);
2540
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2541
- /* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2542
- /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
2543
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2544
- error && /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
2545
- /* @__PURE__ */ jsx10(Box10, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs9(ScrollView5, { ref: scrollRef, children: [
3845
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
3846
+ /* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
3847
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
3848
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
3849
+ error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
3850
+ /* @__PURE__ */ jsx15(Box15, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs14(ScrollView8, { ref: scrollRef, children: [
2546
3851
  existingConfigs.map((config, idx) => {
2547
3852
  const isSelected = selectedExisting === idx;
2548
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
2549
- /* @__PURE__ */ jsxs9(Text9, { color: isSelected ? "yellow" : void 0, bold: isSelected, children: [
3853
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
3854
+ /* @__PURE__ */ jsxs14(Text13, { color: isSelected ? "yellow" : void 0, bold: isSelected, children: [
2550
3855
  isSelected ? "> " : " ",
2551
3856
  config.siteUrl
2552
3857
  ] }),
2553
- /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3858
+ /* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
2554
3859
  " ",
2555
3860
  config.email
2556
3861
  ] })
2557
3862
  ] }, config.siteUrl + config.email);
2558
3863
  }),
2559
- /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs9(
2560
- Text9,
3864
+ /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(
3865
+ Text13,
2561
3866
  {
2562
3867
  color: selectedExisting === existingConfigs.length ? "yellow" : void 0,
2563
3868
  bold: selectedExisting === existingConfigs.length,
@@ -2568,44 +3873,44 @@ function ConfigureJiraSiteModal({
2568
3873
  }
2569
3874
  ) })
2570
3875
  ] }) }),
2571
- loading && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "yellow", children: "Validating credentials..." }) })
3876
+ loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) })
2572
3877
  ] });
2573
3878
  }
2574
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2575
- /* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2576
- /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3879
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
3880
+ /* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Configure Jira Site" }),
3881
+ /* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
2577
3882
  "Up/Down to select, Enter to edit, Esc to ",
2578
3883
  hasExisting ? "go back" : "cancel"
2579
3884
  ] }),
2580
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2581
- error && /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
3885
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
3886
+ error && /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
2582
3887
  renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
2583
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
3888
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
2584
3889
  renderItem("email", "Email", email),
2585
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
3890
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
2586
3891
  renderItem("apiToken", "API Token", apiToken, true),
2587
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2588
- /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs9(Text9, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
3892
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
3893
+ /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(Text13, { color: selectedItem === "submit" ? "green" : void 0, bold: selectedItem === "submit", children: [
2589
3894
  selectedItem === "submit" ? "> " : " ",
2590
3895
  canSubmit ? "[Save Configuration]" : "[Fill all fields first]"
2591
3896
  ] }) }),
2592
- loading && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "yellow", children: "Validating credentials..." }) }),
2593
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens" }) })
3897
+ loading && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "yellow", children: "Validating credentials..." }) }),
3898
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens" }) })
2594
3899
  ] });
2595
3900
  }
2596
3901
 
2597
3902
  // src/components/jira/TicketItem.tsx
2598
- import { Box as Box11, Text as Text10 } from "ink";
2599
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3903
+ import { Box as Box16, Text as Text14 } from "ink";
3904
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2600
3905
  function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
2601
3906
  const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
2602
3907
  const textColor = isSelected ? "green" : void 0;
2603
- return /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text10, { color: textColor, children: [
3908
+ return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs15(Text14, { color: textColor, children: [
2604
3909
  prefix,
2605
- /* @__PURE__ */ jsx11(Text10, { bold: true, color: "blue", children: ticketKey }),
3910
+ /* @__PURE__ */ jsx16(Text14, { bold: true, color: "blue", children: ticketKey }),
2606
3911
  " ",
2607
3912
  summary,
2608
- status && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
3913
+ status && /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
2609
3914
  " [",
2610
3915
  status,
2611
3916
  "]"
@@ -2614,15 +3919,15 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
2614
3919
  }
2615
3920
 
2616
3921
  // src/components/jira/JiraView.tsx
2617
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3922
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2618
3923
  function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated }) {
2619
3924
  const repo = useGitRepo();
2620
3925
  const jira = useJiraTickets();
2621
3926
  const modal = useModal();
2622
3927
  const nav = useListNavigation({ items: jira.tickets });
2623
3928
  const currentTicket = jira.tickets[nav.index] ?? null;
2624
- const lastInitRef = useRef6(null);
2625
- useEffect10(() => {
3929
+ const lastInitRef = useRef8(null);
3930
+ useEffect14(() => {
2626
3931
  if (repo.loading || !repo.repoPath || !repo.currentBranch) return;
2627
3932
  const current = { branch: repo.currentBranch };
2628
3933
  const last = lastInitRef.current;
@@ -2630,17 +3935,17 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2630
3935
  lastInitRef.current = current;
2631
3936
  jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2632
3937
  }, [repo.loading, repo.repoPath, repo.currentBranch, repo.currentRepoSlug, jira.initializeJiraState]);
2633
- useEffect10(() => {
3938
+ useEffect14(() => {
2634
3939
  if (isActive) {
2635
3940
  repo.refreshBranch();
2636
3941
  } else {
2637
3942
  modal.close();
2638
3943
  }
2639
3944
  }, [isActive, repo.refreshBranch, modal.close]);
2640
- useEffect10(() => {
3945
+ useEffect14(() => {
2641
3946
  onModalChange == null ? void 0 : onModalChange(modal.isOpen);
2642
3947
  }, [modal.isOpen, onModalChange]);
2643
- useEffect10(() => {
3948
+ useEffect14(() => {
2644
3949
  onJiraStateChange == null ? void 0 : onJiraStateChange(jira.jiraState);
2645
3950
  }, [jira.jiraState, onJiraStateChange]);
2646
3951
  const handleConfigureSubmit = async (siteUrl, email, apiToken) => {
@@ -2666,7 +3971,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2666
3971
  };
2667
3972
  const handleOpenInBrowser = () => {
2668
3973
  const url = getTicketUrl();
2669
- if (url) open3(url).catch(() => {
3974
+ if (url) open5(url).catch(() => {
2670
3975
  });
2671
3976
  };
2672
3977
  const handleCopyLink = () => {
@@ -2686,7 +3991,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2686
3991
  clearJiraConfig(repo.repoPath);
2687
3992
  jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2688
3993
  };
2689
- useInput9(
3994
+ useInput14(
2690
3995
  (input, key) => {
2691
3996
  if (input === "c" && jira.jiraState === "not_configured") {
2692
3997
  modal.open("configure");
@@ -2712,13 +4017,13 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2712
4017
  { isActive: isActive && !modal.isOpen }
2713
4018
  );
2714
4019
  if (repo.isRepo === false) {
2715
- return /* @__PURE__ */ jsx12(TitledBox4, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx12(Text11, { color: "red", children: "Not a git repository" }) });
4020
+ return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: ["Jira"], flexShrink: 0, children: /* @__PURE__ */ jsx17(Text15, { color: "red", children: "Not a git repository" }) });
2716
4021
  }
2717
4022
  if (modal.type === "configure") {
2718
4023
  const siteUrl = repo.repoPath ? getJiraSiteUrl(repo.repoPath) : void 0;
2719
4024
  const creds = repo.repoPath ? getJiraCredentials(repo.repoPath) : { email: null, apiToken: null };
2720
4025
  const existingConfigs = getExistingJiraConfigs(repo.repoPath ?? void 0);
2721
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4026
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2722
4027
  ConfigureJiraSiteModal,
2723
4028
  {
2724
4029
  initialSiteUrl: siteUrl ?? void 0,
@@ -2735,7 +4040,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2735
4040
  ) });
2736
4041
  }
2737
4042
  if (modal.type === "link") {
2738
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4043
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2739
4044
  LinkTicketModal,
2740
4045
  {
2741
4046
  onSubmit: handleLinkSubmit,
@@ -2749,7 +4054,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2749
4054
  ) });
2750
4055
  }
2751
4056
  if (modal.type === "status" && repo.repoPath && repo.currentBranch && currentTicket) {
2752
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4057
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2753
4058
  ChangeStatusModal,
2754
4059
  {
2755
4060
  repoPath: repo.repoPath,
@@ -2762,10 +4067,10 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2762
4067
  }
2763
4068
  const title = "[4] Jira";
2764
4069
  const borderColor = isActive ? "yellow" : void 0;
2765
- return /* @__PURE__ */ jsx12(TitledBox4, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
2766
- jira.jiraState === "not_configured" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No Jira site configured" }),
2767
- jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No tickets linked to this branch" }),
2768
- jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */ jsx12(
4070
+ return /* @__PURE__ */ jsx17(TitledBox6, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, children: [
4071
+ jira.jiraState === "not_configured" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No Jira site configured" }),
4072
+ jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No tickets linked to this branch" }),
4073
+ jira.jiraState === "has_tickets" && jira.tickets.map((ticket, idx) => /* @__PURE__ */ jsx17(
2769
4074
  TicketItem,
2770
4075
  {
2771
4076
  ticketKey: ticket.key,
@@ -2779,28 +4084,28 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2779
4084
  }
2780
4085
 
2781
4086
  // src/components/logs/LogsView.tsx
2782
- import { useEffect as useEffect12 } from "react";
2783
- import { Box as Box15, useInput as useInput12 } from "ink";
4087
+ import { useEffect as useEffect16 } from "react";
4088
+ import { Box as Box20, useInput as useInput17 } from "ink";
2784
4089
 
2785
4090
  // src/components/logs/LogViewerBox.tsx
2786
- import { useEffect as useEffect11, useRef as useRef7, useState as useState15 } from "react";
2787
- import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
2788
- import { Box as Box13, Text as Text12, useInput as useInput10 } from "ink";
2789
- import { ScrollView as ScrollView6 } from "ink-scroll-view";
2790
- import Spinner2 from "ink-spinner";
4091
+ import { useEffect as useEffect15, useRef as useRef9, useState as useState20 } from "react";
4092
+ import { TitledBox as TitledBox7 } from "@mishieck/ink-titled-box";
4093
+ import { Box as Box18, Text as Text16, useInput as useInput15 } from "ink";
4094
+ import { ScrollView as ScrollView9 } from "ink-scroll-view";
4095
+ import Spinner4 from "ink-spinner";
2791
4096
  import TextInput2 from "ink-text-input";
2792
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4097
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2793
4098
  function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
2794
- const scrollRef = useRef7(null);
2795
- const [isInputMode, setIsInputMode] = useState15(false);
2796
- const [inputValue, setInputValue] = useState15("");
2797
- const [isGeneratingStandup, setIsGeneratingStandup] = useState15(false);
2798
- const [standupResult, setStandupResult] = useState15(null);
2799
- const claudeProcessRef = useRef7(null);
4099
+ const scrollRef = useRef9(null);
4100
+ const [isInputMode, setIsInputMode] = useState20(false);
4101
+ const [inputValue, setInputValue] = useState20("");
4102
+ const [isGeneratingStandup, setIsGeneratingStandup] = useState20(false);
4103
+ const [standupResult, setStandupResult] = useState20(null);
4104
+ const claudeProcessRef = useRef9(null);
2800
4105
  const title = "[6] Log Content";
2801
4106
  const borderColor = isActive ? "yellow" : void 0;
2802
4107
  const displayTitle = date ? `${title} - ${date}.md` : title;
2803
- useInput10(
4108
+ useInput15(
2804
4109
  (input, key) => {
2805
4110
  var _a, _b, _c;
2806
4111
  if (key.escape && isInputMode) {
@@ -2862,7 +4167,7 @@ function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
2862
4167
  },
2863
4168
  { isActive }
2864
4169
  );
2865
- useEffect11(() => {
4170
+ useEffect15(() => {
2866
4171
  return () => {
2867
4172
  var _a;
2868
4173
  (_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
@@ -2885,14 +4190,14 @@ ${value.trim()}
2885
4190
  setIsInputMode(false);
2886
4191
  onRefresh();
2887
4192
  };
2888
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", flexGrow: 1, children: [
2889
- /* @__PURE__ */ jsx13(TitledBox5, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx13(ScrollView6, { ref: scrollRef, children: /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
2890
- !date && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Select a log file to view" }),
2891
- date && content === null && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Log file not found" }),
2892
- date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Empty log file" }),
2893
- date && content && content.trim() !== "" && /* @__PURE__ */ jsx13(Markdown, { children: content })
4193
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", flexGrow: 1, children: [
4194
+ /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx18(ScrollView9, { ref: scrollRef, children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingX: 1, children: [
4195
+ !date && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Select a log file to view" }),
4196
+ date && content === null && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Log file not found" }),
4197
+ date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Empty log file" }),
4198
+ date && content && content.trim() !== "" && /* @__PURE__ */ jsx18(Markdown, { children: content })
2894
4199
  ] }) }) }) }),
2895
- isInputMode && /* @__PURE__ */ jsx13(TitledBox5, { borderStyle: "round", titles: ["Add Entry"], borderColor: "yellow", children: /* @__PURE__ */ jsx13(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx13(
4200
+ isInputMode && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Add Entry"], borderColor: "yellow", children: /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(
2896
4201
  TextInput2,
2897
4202
  {
2898
4203
  value: inputValue,
@@ -2900,22 +4205,22 @@ ${value.trim()}
2900
4205
  onSubmit: handleInputSubmit
2901
4206
  }
2902
4207
  ) }) }),
2903
- isGeneratingStandup && /* @__PURE__ */ jsx13(TitledBox5, { borderStyle: "round", titles: ["Standup Notes"], borderColor: "yellow", children: /* @__PURE__ */ jsxs12(Box13, { paddingX: 1, flexDirection: "column", children: [
2904
- /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
2905
- /* @__PURE__ */ jsx13(Spinner2, { type: "dots" }),
4208
+ isGeneratingStandup && /* @__PURE__ */ jsx18(TitledBox7, { borderStyle: "round", titles: ["Standup Notes"], borderColor: "yellow", children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
4209
+ /* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
4210
+ /* @__PURE__ */ jsx18(Spinner4, { type: "dots" }),
2906
4211
  " Generating standup notes..."
2907
4212
  ] }),
2908
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Press Esc to cancel" })
4213
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to cancel" })
2909
4214
  ] }) }),
2910
- standupResult && /* @__PURE__ */ jsx13(
2911
- TitledBox5,
4215
+ standupResult && /* @__PURE__ */ jsx18(
4216
+ TitledBox7,
2912
4217
  {
2913
4218
  borderStyle: "round",
2914
4219
  titles: ["Standup Notes"],
2915
4220
  borderColor: standupResult.type === "error" ? "red" : "green",
2916
- children: /* @__PURE__ */ jsxs12(Box13, { paddingX: 1, flexDirection: "column", children: [
2917
- standupResult.type === "error" ? /* @__PURE__ */ jsx13(Text12, { color: "red", children: standupResult.message }) : /* @__PURE__ */ jsx13(Markdown, { children: standupResult.message }),
2918
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Press Esc to dismiss" })
4221
+ children: /* @__PURE__ */ jsxs17(Box18, { paddingX: 1, flexDirection: "column", children: [
4222
+ standupResult.type === "error" ? /* @__PURE__ */ jsx18(Text16, { color: "red", children: standupResult.message }) : /* @__PURE__ */ jsx18(Markdown, { children: standupResult.message }),
4223
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to dismiss" })
2919
4224
  ] })
2920
4225
  }
2921
4226
  )
@@ -2923,10 +4228,10 @@ ${value.trim()}
2923
4228
  }
2924
4229
 
2925
4230
  // src/components/logs/LogsHistoryBox.tsx
2926
- import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
2927
- import { Box as Box14, Text as Text13, useInput as useInput11 } from "ink";
2928
- import { ScrollView as ScrollView7 } from "ink-scroll-view";
2929
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
4231
+ import { TitledBox as TitledBox8 } from "@mishieck/ink-titled-box";
4232
+ import { Box as Box19, Text as Text17, useInput as useInput16 } from "ink";
4233
+ import { ScrollView as ScrollView10 } from "ink-scroll-view";
4234
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2930
4235
  function LogsHistoryBox({
2931
4236
  logFiles,
2932
4237
  selectedDate,
@@ -2938,7 +4243,7 @@ function LogsHistoryBox({
2938
4243
  const scrollRef = useScrollToIndex(highlightedIndex);
2939
4244
  const title = "[5] Logs";
2940
4245
  const borderColor = isActive ? "yellow" : void 0;
2941
- useInput11(
4246
+ useInput16(
2942
4247
  (input, key) => {
2943
4248
  if (logFiles.length === 0) return;
2944
4249
  if (key.upArrow || input === "k") {
@@ -2956,44 +4261,44 @@ function LogsHistoryBox({
2956
4261
  },
2957
4262
  { isActive }
2958
4263
  );
2959
- return /* @__PURE__ */ jsx14(TitledBox6, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
2960
- logFiles.length === 0 && /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "No logs yet" }),
2961
- logFiles.length > 0 && /* @__PURE__ */ jsx14(ScrollView7, { ref: scrollRef, children: logFiles.map((file, idx) => {
4264
+ return /* @__PURE__ */ jsx19(TitledBox8, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
4265
+ logFiles.length === 0 && /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No logs yet" }),
4266
+ logFiles.length > 0 && /* @__PURE__ */ jsx19(ScrollView10, { ref: scrollRef, children: logFiles.map((file, idx) => {
2962
4267
  const isHighlighted = idx === highlightedIndex;
2963
4268
  const isSelected = file.date === selectedDate;
2964
4269
  const cursor = isHighlighted ? ">" : " ";
2965
4270
  const indicator = isSelected ? " *" : "";
2966
- return /* @__PURE__ */ jsxs13(Box14, { children: [
2967
- /* @__PURE__ */ jsxs13(Text13, { color: isHighlighted ? "yellow" : void 0, children: [
4271
+ return /* @__PURE__ */ jsxs18(Box19, { children: [
4272
+ /* @__PURE__ */ jsxs18(Text17, { color: isHighlighted ? "yellow" : void 0, children: [
2968
4273
  cursor,
2969
4274
  " "
2970
4275
  ] }),
2971
- /* @__PURE__ */ jsx14(Text13, { color: file.isToday ? "green" : void 0, bold: file.isToday, children: file.date }),
2972
- file.isToday && /* @__PURE__ */ jsx14(Text13, { color: "green", children: " (today)" }),
2973
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: indicator })
4276
+ /* @__PURE__ */ jsx19(Text17, { color: file.isToday ? "green" : void 0, bold: file.isToday, children: file.date }),
4277
+ file.isToday && /* @__PURE__ */ jsx19(Text17, { color: "green", children: " (today)" }),
4278
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: indicator })
2974
4279
  ] }, file.date);
2975
4280
  }) })
2976
4281
  ] }) });
2977
4282
  }
2978
4283
 
2979
4284
  // src/components/logs/LogsView.tsx
2980
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4285
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2981
4286
  function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
2982
4287
  const logs = useLogs();
2983
- useEffect12(() => {
4288
+ useEffect16(() => {
2984
4289
  if (refreshKey !== void 0 && refreshKey > 0) {
2985
4290
  logs.handleExternalLogUpdate();
2986
4291
  }
2987
4292
  }, [refreshKey, logs.handleExternalLogUpdate]);
2988
- useInput12(
4293
+ useInput17(
2989
4294
  (input) => {
2990
4295
  if (input === "5") onFocusedBoxChange("history");
2991
4296
  if (input === "6") onFocusedBoxChange("viewer");
2992
4297
  },
2993
4298
  { isActive }
2994
4299
  );
2995
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", flexGrow: 1, children: [
2996
- /* @__PURE__ */ jsx15(
4300
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
4301
+ /* @__PURE__ */ jsx20(
2997
4302
  LogsHistoryBox,
2998
4303
  {
2999
4304
  logFiles: logs.logFiles,
@@ -3004,7 +4309,7 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
3004
4309
  isActive: isActive && focusedBox === "history"
3005
4310
  }
3006
4311
  ),
3007
- /* @__PURE__ */ jsx15(
4312
+ /* @__PURE__ */ jsx20(
3008
4313
  LogViewerBox,
3009
4314
  {
3010
4315
  date: logs.selectedDate,
@@ -3018,10 +4323,11 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
3018
4323
  }
3019
4324
 
3020
4325
  // src/components/ui/KeybindingsBar.tsx
3021
- import { Box as Box16, Text as Text14 } from "ink";
3022
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
4326
+ import { Box as Box21, Text as Text18 } from "ink";
4327
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
3023
4328
  var globalBindings = [
3024
4329
  { key: "1-4", label: "Focus" },
4330
+ { key: "Tab", label: "Switch Tab" },
3025
4331
  { key: "j/k", label: "Navigate" },
3026
4332
  { key: "Ctrl+C", label: "Quit" }
3027
4333
  ];
@@ -3029,18 +4335,24 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
3029
4335
  var DUCK_ASCII = "<(')___";
3030
4336
  function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
3031
4337
  const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
3032
- return /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, paddingX: 1, gap: 2, children: [
3033
- allBindings.map((binding) => /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
3034
- /* @__PURE__ */ jsx16(Text14, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
3035
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: binding.label })
4338
+ return /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, paddingX: 1, gap: 2, children: [
4339
+ allBindings.map((binding) => /* @__PURE__ */ jsxs20(Box21, { gap: 1, children: [
4340
+ /* @__PURE__ */ jsx21(Text18, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
4341
+ /* @__PURE__ */ jsx21(Text18, { dimColor: true, children: binding.label })
3036
4342
  ] }, binding.key)),
3037
- (duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
3038
- /* @__PURE__ */ jsx16(Text14, { children: DUCK_ASCII }),
3039
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: duck.message })
4343
+ (duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs20(Box21, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
4344
+ /* @__PURE__ */ jsx21(Text18, { children: DUCK_ASCII }),
4345
+ /* @__PURE__ */ jsx21(Text18, { dimColor: true, children: duck.message })
3040
4346
  ] })
3041
4347
  ] });
3042
4348
  }
3043
4349
 
4350
+ // src/constants/tabs.ts
4351
+ var COLUMN2_TABS = [
4352
+ { id: "logs", label: "Logs" },
4353
+ { id: "jira-browser", label: "Jira" }
4354
+ ];
4355
+
3044
4356
  // src/constants/github.ts
3045
4357
  var GITHUB_KEYBINDINGS = {
3046
4358
  remotes: [{ key: "Space", label: "Select Remote" }],
@@ -3058,6 +4370,27 @@ var GITHUB_KEYBINDINGS = {
3058
4370
  ]
3059
4371
  };
3060
4372
 
4373
+ // src/constants/jira-browser.ts
4374
+ var JIRA_BROWSER_KEYBINDINGS = {
4375
+ "saved-views": [
4376
+ { key: "Space", label: "Select" },
4377
+ { key: "a", label: "Add View", color: "green" },
4378
+ { key: "e", label: "Rename" },
4379
+ { key: "d", label: "Delete", color: "red" }
4380
+ ],
4381
+ browser: [
4382
+ { key: "Enter", label: "Details" },
4383
+ { key: "/", label: "Filter" },
4384
+ { key: "u", label: "Unassigned" },
4385
+ { key: "m", label: "Mine" },
4386
+ { key: "x", label: "Clear Filters" },
4387
+ { key: "l", label: "Load More" },
4388
+ { key: "o", label: "Open", color: "green" },
4389
+ { key: "y", label: "Copy Link" },
4390
+ { key: "r", label: "Refresh" }
4391
+ ]
4392
+ };
4393
+
3061
4394
  // src/constants/jira.ts
3062
4395
  var JIRA_KEYBINDINGS = {
3063
4396
  not_configured: [{ key: "c", label: "Configure Jira" }],
@@ -3097,34 +4430,42 @@ function computeKeybindings(focusedView, state) {
3097
4430
  return JIRA_KEYBINDINGS[state.jira.jiraState];
3098
4431
  case "logs":
3099
4432
  return LOGS_KEYBINDINGS[state.logs.focusedBox];
4433
+ case "jira-browser":
4434
+ if (state["jira-browser"].modalOpen) return [];
4435
+ return JIRA_BROWSER_KEYBINDINGS[state["jira-browser"].focusedBox];
3100
4436
  default:
3101
4437
  return [];
3102
4438
  }
3103
4439
  }
3104
4440
 
3105
4441
  // src/app.tsx
3106
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
4442
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
3107
4443
  function App() {
3108
4444
  const { exit } = useApp();
3109
- const [focusedView, setFocusedView] = useState16("github");
3110
- const [modalOpen, setModalOpen] = useState16(false);
3111
- const [logRefreshKey, setLogRefreshKey] = useState16(0);
4445
+ const [focusedView, setFocusedView] = useState21("github");
4446
+ const [modalOpen, setModalOpen] = useState21(false);
4447
+ const [logRefreshKey, setLogRefreshKey] = useState21(0);
4448
+ const [activeTab, setActiveTab] = useState21("logs");
3112
4449
  const duck = useRubberDuck();
3113
- const [githubFocusedBox, setGithubFocusedBox] = useState16("remotes");
3114
- const [jiraState, setJiraState] = useState16("not_configured");
3115
- const [logsFocusedBox, setLogsFocusedBox] = useState16("history");
3116
- const keybindings = useMemo2(
4450
+ const [githubFocusedBox, setGithubFocusedBox] = useState21("remotes");
4451
+ const [jiraState, setJiraState] = useState21("not_configured");
4452
+ const [logsFocusedBox, setLogsFocusedBox] = useState21("history");
4453
+ const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState21("saved-views");
4454
+ const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState21(false);
4455
+ const keybindings = useMemo4(
3117
4456
  () => computeKeybindings(focusedView, {
3118
4457
  github: { focusedBox: githubFocusedBox },
3119
4458
  jira: { jiraState, modalOpen },
3120
- logs: { focusedBox: logsFocusedBox }
4459
+ logs: { focusedBox: logsFocusedBox },
4460
+ "jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
3121
4461
  }),
3122
- [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox]
4462
+ [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox, jiraBrowserFocusedBox, jiraBrowserModalOpen]
3123
4463
  );
3124
- const handleLogUpdated = useCallback9(() => {
4464
+ const handleLogUpdated = useCallback11(() => {
3125
4465
  setLogRefreshKey((prev) => prev + 1);
3126
4466
  }, []);
3127
- useInput13(
4467
+ const anyModalOpen = modalOpen || jiraBrowserModalOpen;
4468
+ useInput18(
3128
4469
  (input, key) => {
3129
4470
  if (key.ctrl && input === "c") {
3130
4471
  exit();
@@ -3136,12 +4477,23 @@ function App() {
3136
4477
  setFocusedView("jira");
3137
4478
  }
3138
4479
  if (input === "5") {
3139
- setFocusedView("logs");
3140
- setLogsFocusedBox("history");
4480
+ setFocusedView(activeTab);
4481
+ if (activeTab === "logs") setLogsFocusedBox("history");
4482
+ if (activeTab === "jira-browser") setJiraBrowserFocusedBox("saved-views");
3141
4483
  }
3142
4484
  if (input === "6") {
3143
- setFocusedView("logs");
3144
- setLogsFocusedBox("viewer");
4485
+ setFocusedView(activeTab);
4486
+ if (activeTab === "logs") setLogsFocusedBox("viewer");
4487
+ if (activeTab === "jira-browser") setJiraBrowserFocusedBox("browser");
4488
+ }
4489
+ if (key.tab) {
4490
+ setActiveTab((current) => {
4491
+ const idx = COLUMN2_TABS.findIndex((t) => t.id === current);
4492
+ const next = (idx + 1) % COLUMN2_TABS.length;
4493
+ const nextTab = COLUMN2_TABS[next];
4494
+ setFocusedView(nextTab.id);
4495
+ return nextTab.id;
4496
+ });
3145
4497
  }
3146
4498
  if (input === "d") {
3147
4499
  duck.toggleDuck();
@@ -3150,12 +4502,19 @@ function App() {
3150
4502
  duck.quack();
3151
4503
  }
3152
4504
  },
3153
- { isActive: !modalOpen }
4505
+ { isActive: !anyModalOpen }
3154
4506
  );
3155
- return /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
3156
- /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
3157
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
3158
- /* @__PURE__ */ jsx17(
4507
+ return /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
4508
+ /* @__PURE__ */ jsxs21(Box22, { height: 1, flexDirection: "row", columnGap: 1, children: [
4509
+ /* @__PURE__ */ jsx22(Box22, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Current branch" }) }),
4510
+ /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
4511
+ /* @__PURE__ */ jsx22(Text19, { color: "gray", children: "Dashboards" }),
4512
+ COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx22(Text19, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
4513
+ ] })
4514
+ ] }),
4515
+ /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
4516
+ /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
4517
+ /* @__PURE__ */ jsx22(
3159
4518
  GitHubView,
3160
4519
  {
3161
4520
  isActive: focusedView === "github",
@@ -3163,7 +4522,7 @@ function App() {
3163
4522
  onLogUpdated: handleLogUpdated
3164
4523
  }
3165
4524
  ),
3166
- /* @__PURE__ */ jsx17(
4525
+ /* @__PURE__ */ jsx22(
3167
4526
  JiraView,
3168
4527
  {
3169
4528
  isActive: focusedView === "jira",
@@ -3173,21 +4532,33 @@ function App() {
3173
4532
  }
3174
4533
  )
3175
4534
  ] }),
3176
- /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsx17(
3177
- LogsView,
3178
- {
3179
- isActive: focusedView === "logs",
3180
- refreshKey: logRefreshKey,
3181
- focusedBox: logsFocusedBox,
3182
- onFocusedBoxChange: setLogsFocusedBox
3183
- }
3184
- ) })
4535
+ /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
4536
+ activeTab === "logs" && /* @__PURE__ */ jsx22(
4537
+ LogsView,
4538
+ {
4539
+ isActive: focusedView === "logs",
4540
+ refreshKey: logRefreshKey,
4541
+ focusedBox: logsFocusedBox,
4542
+ onFocusedBoxChange: setLogsFocusedBox
4543
+ }
4544
+ ),
4545
+ activeTab === "jira-browser" && /* @__PURE__ */ jsx22(
4546
+ JiraBrowserView,
4547
+ {
4548
+ isActive: focusedView === "jira-browser",
4549
+ focusedBox: jiraBrowserFocusedBox,
4550
+ onFocusedBoxChange: setJiraBrowserFocusedBox,
4551
+ onModalChange: setJiraBrowserModalOpen,
4552
+ onLogUpdated: handleLogUpdated
4553
+ }
4554
+ )
4555
+ ] })
3185
4556
  ] }),
3186
- /* @__PURE__ */ jsx17(
4557
+ /* @__PURE__ */ jsx22(
3187
4558
  KeybindingsBar,
3188
4559
  {
3189
4560
  contextBindings: keybindings,
3190
- modalOpen,
4561
+ modalOpen: anyModalOpen,
3191
4562
  duck: { visible: duck.visible, message: duck.message }
3192
4563
  }
3193
4564
  )
@@ -3198,31 +4569,31 @@ function App() {
3198
4569
  import { render as inkRender } from "ink";
3199
4570
 
3200
4571
  // src/lib/Screen.tsx
3201
- import { useCallback as useCallback10, useEffect as useEffect13, useState as useState17 } from "react";
3202
- import { Box as Box18, useStdout as useStdout2 } from "ink";
3203
- import { jsx as jsx18 } from "react/jsx-runtime";
4572
+ import { useCallback as useCallback12, useEffect as useEffect17, useState as useState22 } from "react";
4573
+ import { Box as Box23, useStdout as useStdout2 } from "ink";
4574
+ import { jsx as jsx23 } from "react/jsx-runtime";
3204
4575
  function Screen({ children }) {
3205
4576
  const { stdout } = useStdout2();
3206
- const getSize = useCallback10(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
3207
- const [size, setSize] = useState17(getSize);
3208
- useEffect13(() => {
4577
+ const getSize = useCallback12(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
4578
+ const [size, setSize] = useState22(getSize);
4579
+ useEffect17(() => {
3209
4580
  const onResize = () => setSize(getSize());
3210
4581
  stdout.on("resize", onResize);
3211
4582
  return () => {
3212
4583
  stdout.off("resize", onResize);
3213
4584
  };
3214
4585
  }, [stdout, getSize]);
3215
- return /* @__PURE__ */ jsx18(Box18, { height: size.height, width: size.width, children });
4586
+ return /* @__PURE__ */ jsx23(Box23, { height: size.height, width: size.width, children });
3216
4587
  }
3217
4588
 
3218
4589
  // src/lib/render.tsx
3219
- import { jsx as jsx19 } from "react/jsx-runtime";
4590
+ import { jsx as jsx24 } from "react/jsx-runtime";
3220
4591
  var ENTER_ALT_BUFFER = "\x1B[?1049h";
3221
4592
  var EXIT_ALT_BUFFER = "\x1B[?1049l";
3222
4593
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
3223
4594
  function render(node, options) {
3224
4595
  process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
3225
- const element = /* @__PURE__ */ jsx19(Screen, { children: node });
4596
+ const element = /* @__PURE__ */ jsx24(Screen, { children: node });
3226
4597
  const instance = inkRender(element, options);
3227
4598
  setImmediate(() => instance.rerender(element));
3228
4599
  const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
@@ -3243,7 +4614,7 @@ function render(node, options) {
3243
4614
  }
3244
4615
 
3245
4616
  // src/cli.tsx
3246
- import { jsx as jsx20 } from "react/jsx-runtime";
4617
+ import { jsx as jsx25 } from "react/jsx-runtime";
3247
4618
  var cli = meow(
3248
4619
  `
3249
4620
  Usage
@@ -3276,4 +4647,4 @@ if (cli.flags.cwd) {
3276
4647
  process.exit(1);
3277
4648
  }
3278
4649
  }
3279
- render(/* @__PURE__ */ jsx20(App, {}));
4650
+ render(/* @__PURE__ */ jsx25(App, {}));