clairo 1.2.3 → 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 +1612 -246
  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";
@@ -866,7 +866,8 @@ function createAuthHeader(email, apiToken) {
866
866
  return `Basic ${credentials}`;
867
867
  }
868
868
  async function jiraFetch(auth, endpoint, options) {
869
- 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}`;
870
871
  const method = (options == null ? void 0 : options.method) ?? "GET";
871
872
  try {
872
873
  const headers = {
@@ -911,6 +912,21 @@ async function validateCredentials(auth) {
911
912
  }
912
913
  return { success: true, data: result.data };
913
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
+ }
914
930
  async function getIssue(auth, ticketKey) {
915
931
  const result = await jiraFetch(auth, `/issue/${ticketKey}?fields=summary,status`);
916
932
  if (!result.ok) {
@@ -990,6 +1006,377 @@ async function applyTransition(auth, ticketKey, transitionId) {
990
1006
  }
991
1007
  return { success: true, data: null };
992
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
+ }
993
1380
 
994
1381
  // src/lib/logs/index.ts
995
1382
  import { spawnSync } from "child_process";
@@ -1116,6 +1503,18 @@ function logJiraStatusChanged(ticketKey, ticketName, oldStatus, newStatus) {
1116
1503
  ${ticketKey}: ${ticketName}
1117
1504
  ${oldStatus} \u2192 ${newStatus}
1118
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
+
1119
1518
  `;
1120
1519
  appendToLog(today, entry);
1121
1520
  }
@@ -1659,13 +2058,13 @@ function useLogs() {
1659
2058
  import { useCallback as useCallback6, useState as useState7 } from "react";
1660
2059
  function useModal() {
1661
2060
  const [modalType, setModalType] = useState7("none");
1662
- const open4 = useCallback6((type) => setModalType(type), []);
2061
+ const open6 = useCallback6((type) => setModalType(type), []);
1663
2062
  const close = useCallback6(() => setModalType("none"), []);
1664
2063
  const isOpen = modalType !== "none";
1665
2064
  return {
1666
2065
  type: modalType,
1667
2066
  isOpen,
1668
- open: open4,
2067
+ open: open6,
1669
2068
  close
1670
2069
  };
1671
2070
  }
@@ -1771,7 +2170,9 @@ var REACTION_MESSAGES = {
1771
2170
  error: ["Uh oh...", "There there...", "*concerned quacking*", "Quack... not good."],
1772
2171
  "jira:transition": ["Ticket moving!", "Progress!", "Workflow in motion!"],
1773
2172
  "jira:linked": ["Ticket linked!", "Jira connection made!", "Tracking enabled!"],
1774
- "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!"]
1775
2176
  };
1776
2177
  function useRubberDuck() {
1777
2178
  const [state, setState] = useState9({
@@ -2244,11 +2645,11 @@ ${body}`;
2244
2645
  ] });
2245
2646
  }
2246
2647
 
2247
- // src/components/jira/JiraView.tsx
2248
- import open3 from "open";
2249
- 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";
2250
2651
 
2251
- // src/components/jira/LinkTicketModal.tsx
2652
+ // src/components/jira-browser/AddViewModal.tsx
2252
2653
  import { useState as useState12 } from "react";
2253
2654
  import { Box as Box8, Text as Text7, useInput as useInput6 } from "ink";
2254
2655
 
@@ -2267,8 +2668,11 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
2267
2668
  if (key.return || key.escape || key.upArrow || key.downArrow || key.tab) {
2268
2669
  return;
2269
2670
  }
2270
- if (input && input.length === 1 && input.charCodeAt(0) >= 32) {
2271
- 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
+ }
2272
2676
  }
2273
2677
  },
2274
2678
  { isActive }
@@ -2281,11 +2685,13 @@ function TextInput({ value, onChange, placeholder, isActive, mask }) {
2281
2685
  ] }) });
2282
2686
  }
2283
2687
 
2284
- // src/components/jira/LinkTicketModal.tsx
2688
+ // src/components/jira-browser/AddViewModal.tsx
2285
2689
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2286
- function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
2287
- const [ticketInput, setTicketInput] = useState12("");
2288
- 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;
2289
2695
  useInput6(
2290
2696
  (_input, key) => {
2291
2697
  if (loading) return;
@@ -2293,120 +2699,1014 @@ function LinkTicketModal({ onSubmit, onCancel, loading, error }) {
2293
2699
  onCancel();
2294
2700
  return;
2295
2701
  }
2702
+ if (key.tab) {
2703
+ setActiveField((f) => f === "url" ? "name" : "url");
2704
+ return;
2705
+ }
2296
2706
  if (key.return && canSubmit) {
2297
- onSubmit(ticketInput.trim());
2707
+ const viewName = name.trim() || generateViewName(url.trim());
2708
+ onSubmit(url.trim(), viewName);
2298
2709
  }
2299
2710
  },
2300
2711
  { isActive: !loading }
2301
2712
  );
2302
2713
  return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
2303
- /* @__PURE__ */ jsx8(Text7, { bold: true, color: "yellow", children: "Link Jira Ticket" }),
2304
- /* @__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" }),
2305
2716
  /* @__PURE__ */ jsx8(Box8, { marginTop: 1 }),
2306
2717
  error && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "red", children: error }) }),
2307
2718
  /* @__PURE__ */ jsxs7(Box8, { children: [
2308
- /* @__PURE__ */ jsx8(Text7, { color: "blue", children: "Ticket: " }),
2309
- /* @__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
+ )
2310
2741
  ] }),
2311
- loading && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: "yellow", children: "Fetching ticket..." }) }),
2312
- /* @__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" }) })
2313
2744
  ] });
2314
2745
  }
2315
2746
 
2316
- // 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";
2317
2750
  import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
2318
- 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";
2319
2754
 
2320
- // src/components/jira/ChangeStatusModal.tsx
2321
- 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";
2322
2758
  import { Box as Box9, Text as Text8, useInput as useInput7 } from "ink";
2759
+ import { ScrollView as ScrollView5 } from "ink-scroll-view";
2323
2760
  import SelectInput from "ink-select-input";
2761
+ import Spinner2 from "ink-spinner";
2324
2762
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2325
- function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onCancel }) {
2326
- 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);
2327
2777
  const [loading, setLoading] = useState13(true);
2328
- const [applying, setApplying] = useState13(false);
2329
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);
2330
2784
  useEffect9(() => {
2331
- const fetchTransitions = async () => {
2332
- const siteUrl = getJiraSiteUrl(repoPath);
2333
- const creds = getJiraCredentials(repoPath);
2334
- if (!siteUrl || !creds.email || !creds.apiToken) {
2335
- setError("Jira not configured");
2336
- duckEvents.emit("error");
2337
- setLoading(false);
2338
- return;
2339
- }
2340
- const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
2341
- const result = await getTransitions(auth, ticketKey);
2785
+ setLoading(true);
2786
+ setError(null);
2787
+ getIssueDetail(auth, issueKey).then((result) => {
2342
2788
  if (result.success) {
2343
- setTransitions(result.data);
2789
+ setDetail(result.data);
2344
2790
  } else {
2345
2791
  setError(result.error);
2346
- duckEvents.emit("error");
2347
2792
  }
2348
2793
  setLoading(false);
2349
- };
2350
- fetchTransitions();
2351
- }, [repoPath, ticketKey]);
2352
- const handleSelect = async (item) => {
2353
- setApplying(true);
2354
- setError(null);
2355
- const siteUrl = getJiraSiteUrl(repoPath);
2356
- const creds = getJiraCredentials(repoPath);
2357
- if (!siteUrl || !creds.email || !creds.apiToken) {
2358
- setError("Jira not configured");
2359
- duckEvents.emit("error");
2360
- setApplying(false);
2361
- 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);
2362
2806
  }
2363
- const auth = { siteUrl, email: creds.email, apiToken: creds.apiToken };
2364
- 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);
2365
2814
  if (result.success) {
2366
2815
  const transition = transitions.find((t) => t.id === item.value);
2367
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 });
2368
2820
  duckEvents.emit("jira:transition");
2369
- onComplete(newStatus);
2821
+ logJiraStatusChanged(issueKey, issueSummary, oldStatus, newStatus);
2822
+ onLogUpdated == null ? void 0 : onLogUpdated();
2370
2823
  } else {
2371
- 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);
2372
2843
  duckEvents.emit("error");
2373
- setApplying(false);
2374
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);
2375
2862
  };
2376
2863
  useInput7(
2377
- (_input, key) => {
2378
- if (key.escape && !applying) {
2379
- onCancel();
2864
+ (input, key) => {
2865
+ var _a2, _b;
2866
+ if (mode === "transitions") {
2867
+ if (key.escape) {
2868
+ setMode("normal");
2869
+ }
2870
+ return;
2380
2871
  }
2381
- },
2382
- { isActive: !applying }
2383
- );
2384
- const items = transitions.map((t) => ({
2385
- label: t.name,
2386
- value: t.id
2387
- }));
2388
- const initialIndex = Math.max(
2389
- 0,
2390
- transitions.findIndex((t) => t.to.name === currentStatus)
2391
- );
2392
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [
2393
- /* @__PURE__ */ jsxs8(Text8, { bold: true, color: "yellow", children: [
2394
- "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: ",
2395
3695
  ticketKey
2396
3696
  ] }),
2397
- loading && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Loading transitions..." }),
2398
- error && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "red", children: error }) }),
2399
- !loading && !error && transitions.length === 0 && /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "No available transitions" }),
2400
- !loading && !error && transitions.length > 0 && !applying && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx9(SelectInput, { items, initialIndex, onSelect: handleSelect }) }),
2401
- applying && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "yellow", children: "Updating status..." }) }),
2402
- /* @__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" }) })
2403
3703
  ] });
2404
3704
  }
2405
3705
 
2406
3706
  // src/components/jira/ConfigureJiraSiteModal.tsx
2407
- import { useState as useState14 } from "react";
2408
- import { Box as Box10, Text as Text9, useInput as useInput8 } from "ink";
2409
- 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";
2410
3710
 
2411
3711
  // src/lib/editor.ts
2412
3712
  import { spawnSync as spawnSync2 } from "child_process";
@@ -2437,7 +3737,7 @@ function openInEditor(content, filename) {
2437
3737
  }
2438
3738
 
2439
3739
  // src/components/jira/ConfigureJiraSiteModal.tsx
2440
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3740
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2441
3741
  var MAX_VISIBLE_ITEMS = 4;
2442
3742
  function ConfigureJiraSiteModal({
2443
3743
  initialSiteUrl,
@@ -2449,17 +3749,17 @@ function ConfigureJiraSiteModal({
2449
3749
  error
2450
3750
  }) {
2451
3751
  const hasExisting = existingConfigs.length > 0;
2452
- const [mode, setMode] = useState14(hasExisting ? "choose" : "manual");
2453
- const [selectedExisting, setSelectedExisting] = useState14(0);
3752
+ const [mode, setMode] = useState19(hasExisting ? "choose" : "manual");
3753
+ const [selectedExisting, setSelectedExisting] = useState19(0);
2454
3754
  const scrollRef = useScrollToIndex(selectedExisting);
2455
- const [siteUrl, setSiteUrl] = useState14(initialSiteUrl ?? "");
2456
- const [email, setEmail] = useState14(initialEmail ?? "");
2457
- const [apiToken, setApiToken] = useState14("");
2458
- 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");
2459
3759
  const items = ["siteUrl", "email", "apiToken", "submit"];
2460
3760
  const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
2461
3761
  const chooseItems = existingConfigs.length + 1;
2462
- useInput8(
3762
+ useInput13(
2463
3763
  (input, key) => {
2464
3764
  if (loading) return;
2465
3765
  if (key.escape) {
@@ -2531,38 +3831,38 @@ function ConfigureJiraSiteModal({
2531
3831
  const prefix = isSelected ? "> " : " ";
2532
3832
  const color = isSelected ? "yellow" : void 0;
2533
3833
  const displayValue = isSensitive && value ? "*".repeat(Math.min(value.length, 20)) : value;
2534
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
2535
- /* @__PURE__ */ jsxs9(Text9, { color, bold: isSelected, children: [
3834
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
3835
+ /* @__PURE__ */ jsxs14(Text13, { color, bold: isSelected, children: [
2536
3836
  prefix,
2537
3837
  label
2538
3838
  ] }),
2539
- 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)" }) })
2540
3840
  ] });
2541
3841
  };
2542
3842
  if (mode === "choose") {
2543
3843
  const totalItems = existingConfigs.length + 1;
2544
3844
  const listHeight = Math.min(totalItems * 2, MAX_VISIBLE_ITEMS * 2);
2545
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2546
- /* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2547
- /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
2548
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2549
- error && /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "red", children: error }) }),
2550
- /* @__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: [
2551
3851
  existingConfigs.map((config, idx) => {
2552
3852
  const isSelected = selectedExisting === idx;
2553
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
2554
- /* @__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: [
2555
3855
  isSelected ? "> " : " ",
2556
3856
  config.siteUrl
2557
3857
  ] }),
2558
- /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3858
+ /* @__PURE__ */ jsxs14(Text13, { dimColor: true, children: [
2559
3859
  " ",
2560
3860
  config.email
2561
3861
  ] })
2562
3862
  ] }, config.siteUrl + config.email);
2563
3863
  }),
2564
- /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs9(
2565
- Text9,
3864
+ /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs14(
3865
+ Text13,
2566
3866
  {
2567
3867
  color: selectedExisting === existingConfigs.length ? "yellow" : void 0,
2568
3868
  bold: selectedExisting === existingConfigs.length,
@@ -2573,44 +3873,44 @@ function ConfigureJiraSiteModal({
2573
3873
  }
2574
3874
  ) })
2575
3875
  ] }) }),
2576
- 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..." }) })
2577
3877
  ] });
2578
3878
  }
2579
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2580
- /* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2581
- /* @__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: [
2582
3882
  "Up/Down to select, Enter to edit, Esc to ",
2583
3883
  hasExisting ? "go back" : "cancel"
2584
3884
  ] }),
2585
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2586
- 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 }) }),
2587
3887
  renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
2588
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
3888
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
2589
3889
  renderItem("email", "Email", email),
2590
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
3890
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1 }),
2591
3891
  renderItem("apiToken", "API Token", apiToken, true),
2592
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1 }),
2593
- /* @__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: [
2594
3894
  selectedItem === "submit" ? "> " : " ",
2595
3895
  canSubmit ? "[Save Configuration]" : "[Fill all fields first]"
2596
3896
  ] }) }),
2597
- loading && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { color: "yellow", children: "Validating credentials..." }) }),
2598
- /* @__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" }) })
2599
3899
  ] });
2600
3900
  }
2601
3901
 
2602
3902
  // src/components/jira/TicketItem.tsx
2603
- import { Box as Box11, Text as Text10 } from "ink";
2604
- 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";
2605
3905
  function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
2606
3906
  const prefix = isHighlighted ? "> " : isSelected ? "\u25CF " : " ";
2607
3907
  const textColor = isSelected ? "green" : void 0;
2608
- return /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text10, { color: textColor, children: [
3908
+ return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs15(Text14, { color: textColor, children: [
2609
3909
  prefix,
2610
- /* @__PURE__ */ jsx11(Text10, { bold: true, color: "blue", children: ticketKey }),
3910
+ /* @__PURE__ */ jsx16(Text14, { bold: true, color: "blue", children: ticketKey }),
2611
3911
  " ",
2612
3912
  summary,
2613
- status && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
3913
+ status && /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
2614
3914
  " [",
2615
3915
  status,
2616
3916
  "]"
@@ -2619,15 +3919,15 @@ function TicketItem({ ticketKey, summary, status, isHighlighted, isSelected }) {
2619
3919
  }
2620
3920
 
2621
3921
  // src/components/jira/JiraView.tsx
2622
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3922
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2623
3923
  function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated }) {
2624
3924
  const repo = useGitRepo();
2625
3925
  const jira = useJiraTickets();
2626
3926
  const modal = useModal();
2627
3927
  const nav = useListNavigation({ items: jira.tickets });
2628
3928
  const currentTicket = jira.tickets[nav.index] ?? null;
2629
- const lastInitRef = useRef6(null);
2630
- useEffect10(() => {
3929
+ const lastInitRef = useRef8(null);
3930
+ useEffect14(() => {
2631
3931
  if (repo.loading || !repo.repoPath || !repo.currentBranch) return;
2632
3932
  const current = { branch: repo.currentBranch };
2633
3933
  const last = lastInitRef.current;
@@ -2635,17 +3935,17 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2635
3935
  lastInitRef.current = current;
2636
3936
  jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2637
3937
  }, [repo.loading, repo.repoPath, repo.currentBranch, repo.currentRepoSlug, jira.initializeJiraState]);
2638
- useEffect10(() => {
3938
+ useEffect14(() => {
2639
3939
  if (isActive) {
2640
3940
  repo.refreshBranch();
2641
3941
  } else {
2642
3942
  modal.close();
2643
3943
  }
2644
3944
  }, [isActive, repo.refreshBranch, modal.close]);
2645
- useEffect10(() => {
3945
+ useEffect14(() => {
2646
3946
  onModalChange == null ? void 0 : onModalChange(modal.isOpen);
2647
3947
  }, [modal.isOpen, onModalChange]);
2648
- useEffect10(() => {
3948
+ useEffect14(() => {
2649
3949
  onJiraStateChange == null ? void 0 : onJiraStateChange(jira.jiraState);
2650
3950
  }, [jira.jiraState, onJiraStateChange]);
2651
3951
  const handleConfigureSubmit = async (siteUrl, email, apiToken) => {
@@ -2671,7 +3971,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2671
3971
  };
2672
3972
  const handleOpenInBrowser = () => {
2673
3973
  const url = getTicketUrl();
2674
- if (url) open3(url).catch(() => {
3974
+ if (url) open5(url).catch(() => {
2675
3975
  });
2676
3976
  };
2677
3977
  const handleCopyLink = () => {
@@ -2691,7 +3991,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2691
3991
  clearJiraConfig(repo.repoPath);
2692
3992
  jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2693
3993
  };
2694
- useInput9(
3994
+ useInput14(
2695
3995
  (input, key) => {
2696
3996
  if (input === "c" && jira.jiraState === "not_configured") {
2697
3997
  modal.open("configure");
@@ -2717,13 +4017,13 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2717
4017
  { isActive: isActive && !modal.isOpen }
2718
4018
  );
2719
4019
  if (repo.isRepo === false) {
2720
- 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" }) });
2721
4021
  }
2722
4022
  if (modal.type === "configure") {
2723
4023
  const siteUrl = repo.repoPath ? getJiraSiteUrl(repo.repoPath) : void 0;
2724
4024
  const creds = repo.repoPath ? getJiraCredentials(repo.repoPath) : { email: null, apiToken: null };
2725
4025
  const existingConfigs = getExistingJiraConfigs(repo.repoPath ?? void 0);
2726
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4026
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2727
4027
  ConfigureJiraSiteModal,
2728
4028
  {
2729
4029
  initialSiteUrl: siteUrl ?? void 0,
@@ -2740,7 +4040,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2740
4040
  ) });
2741
4041
  }
2742
4042
  if (modal.type === "link") {
2743
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4043
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2744
4044
  LinkTicketModal,
2745
4045
  {
2746
4046
  onSubmit: handleLinkSubmit,
@@ -2754,7 +4054,7 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2754
4054
  ) });
2755
4055
  }
2756
4056
  if (modal.type === "status" && repo.repoPath && repo.currentBranch && currentTicket) {
2757
- return /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx12(
4057
+ return /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx17(
2758
4058
  ChangeStatusModal,
2759
4059
  {
2760
4060
  repoPath: repo.repoPath,
@@ -2767,10 +4067,10 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2767
4067
  }
2768
4068
  const title = "[4] Jira";
2769
4069
  const borderColor = isActive ? "yellow" : void 0;
2770
- return /* @__PURE__ */ jsx12(TitledBox4, { borderStyle: "round", titles: [title], borderColor, flexShrink: 0, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
2771
- jira.jiraState === "not_configured" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No Jira site configured" }),
2772
- jira.jiraState === "no_tickets" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No tickets linked to this branch" }),
2773
- 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(
2774
4074
  TicketItem,
2775
4075
  {
2776
4076
  ticketKey: ticket.key,
@@ -2784,28 +4084,28 @@ function JiraView({ isActive, onModalChange, onJiraStateChange, onLogUpdated })
2784
4084
  }
2785
4085
 
2786
4086
  // src/components/logs/LogsView.tsx
2787
- import { useEffect as useEffect12 } from "react";
2788
- 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";
2789
4089
 
2790
4090
  // src/components/logs/LogViewerBox.tsx
2791
- import { useEffect as useEffect11, useRef as useRef7, useState as useState15 } from "react";
2792
- import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
2793
- import { Box as Box13, Text as Text12, useInput as useInput10 } from "ink";
2794
- import { ScrollView as ScrollView6 } from "ink-scroll-view";
2795
- 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";
2796
4096
  import TextInput2 from "ink-text-input";
2797
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4097
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2798
4098
  function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
2799
- const scrollRef = useRef7(null);
2800
- const [isInputMode, setIsInputMode] = useState15(false);
2801
- const [inputValue, setInputValue] = useState15("");
2802
- const [isGeneratingStandup, setIsGeneratingStandup] = useState15(false);
2803
- const [standupResult, setStandupResult] = useState15(null);
2804
- 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);
2805
4105
  const title = "[6] Log Content";
2806
4106
  const borderColor = isActive ? "yellow" : void 0;
2807
4107
  const displayTitle = date ? `${title} - ${date}.md` : title;
2808
- useInput10(
4108
+ useInput15(
2809
4109
  (input, key) => {
2810
4110
  var _a, _b, _c;
2811
4111
  if (key.escape && isInputMode) {
@@ -2867,7 +4167,7 @@ function LogViewerBox({ date, content, isActive, onRefresh, onLogCreated }) {
2867
4167
  },
2868
4168
  { isActive }
2869
4169
  );
2870
- useEffect11(() => {
4170
+ useEffect15(() => {
2871
4171
  return () => {
2872
4172
  var _a;
2873
4173
  (_a = claudeProcessRef.current) == null ? void 0 : _a.cancel();
@@ -2890,14 +4190,14 @@ ${value.trim()}
2890
4190
  setIsInputMode(false);
2891
4191
  onRefresh();
2892
4192
  };
2893
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", flexGrow: 1, children: [
2894
- /* @__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: [
2895
- !date && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Select a log file to view" }),
2896
- date && content === null && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Log file not found" }),
2897
- date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Empty log file" }),
2898
- 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 })
2899
4199
  ] }) }) }) }),
2900
- 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(
2901
4201
  TextInput2,
2902
4202
  {
2903
4203
  value: inputValue,
@@ -2905,22 +4205,22 @@ ${value.trim()}
2905
4205
  onSubmit: handleInputSubmit
2906
4206
  }
2907
4207
  ) }) }),
2908
- isGeneratingStandup && /* @__PURE__ */ jsx13(TitledBox5, { borderStyle: "round", titles: ["Standup Notes"], borderColor: "yellow", children: /* @__PURE__ */ jsxs12(Box13, { paddingX: 1, flexDirection: "column", children: [
2909
- /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
2910
- /* @__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" }),
2911
4211
  " Generating standup notes..."
2912
4212
  ] }),
2913
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Press Esc to cancel" })
4213
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press Esc to cancel" })
2914
4214
  ] }) }),
2915
- standupResult && /* @__PURE__ */ jsx13(
2916
- TitledBox5,
4215
+ standupResult && /* @__PURE__ */ jsx18(
4216
+ TitledBox7,
2917
4217
  {
2918
4218
  borderStyle: "round",
2919
4219
  titles: ["Standup Notes"],
2920
4220
  borderColor: standupResult.type === "error" ? "red" : "green",
2921
- children: /* @__PURE__ */ jsxs12(Box13, { paddingX: 1, flexDirection: "column", children: [
2922
- standupResult.type === "error" ? /* @__PURE__ */ jsx13(Text12, { color: "red", children: standupResult.message }) : /* @__PURE__ */ jsx13(Markdown, { children: standupResult.message }),
2923
- /* @__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" })
2924
4224
  ] })
2925
4225
  }
2926
4226
  )
@@ -2928,10 +4228,10 @@ ${value.trim()}
2928
4228
  }
2929
4229
 
2930
4230
  // src/components/logs/LogsHistoryBox.tsx
2931
- import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
2932
- import { Box as Box14, Text as Text13, useInput as useInput11 } from "ink";
2933
- import { ScrollView as ScrollView7 } from "ink-scroll-view";
2934
- 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";
2935
4235
  function LogsHistoryBox({
2936
4236
  logFiles,
2937
4237
  selectedDate,
@@ -2943,7 +4243,7 @@ function LogsHistoryBox({
2943
4243
  const scrollRef = useScrollToIndex(highlightedIndex);
2944
4244
  const title = "[5] Logs";
2945
4245
  const borderColor = isActive ? "yellow" : void 0;
2946
- useInput11(
4246
+ useInput16(
2947
4247
  (input, key) => {
2948
4248
  if (logFiles.length === 0) return;
2949
4249
  if (key.upArrow || input === "k") {
@@ -2961,44 +4261,44 @@ function LogsHistoryBox({
2961
4261
  },
2962
4262
  { isActive }
2963
4263
  );
2964
- return /* @__PURE__ */ jsx14(TitledBox6, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
2965
- logFiles.length === 0 && /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "No logs yet" }),
2966
- 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) => {
2967
4267
  const isHighlighted = idx === highlightedIndex;
2968
4268
  const isSelected = file.date === selectedDate;
2969
4269
  const cursor = isHighlighted ? ">" : " ";
2970
4270
  const indicator = isSelected ? " *" : "";
2971
- return /* @__PURE__ */ jsxs13(Box14, { children: [
2972
- /* @__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: [
2973
4273
  cursor,
2974
4274
  " "
2975
4275
  ] }),
2976
- /* @__PURE__ */ jsx14(Text13, { color: file.isToday ? "green" : void 0, bold: file.isToday, children: file.date }),
2977
- file.isToday && /* @__PURE__ */ jsx14(Text13, { color: "green", children: " (today)" }),
2978
- /* @__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 })
2979
4279
  ] }, file.date);
2980
4280
  }) })
2981
4281
  ] }) });
2982
4282
  }
2983
4283
 
2984
4284
  // src/components/logs/LogsView.tsx
2985
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4285
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2986
4286
  function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
2987
4287
  const logs = useLogs();
2988
- useEffect12(() => {
4288
+ useEffect16(() => {
2989
4289
  if (refreshKey !== void 0 && refreshKey > 0) {
2990
4290
  logs.handleExternalLogUpdate();
2991
4291
  }
2992
4292
  }, [refreshKey, logs.handleExternalLogUpdate]);
2993
- useInput12(
4293
+ useInput17(
2994
4294
  (input) => {
2995
4295
  if (input === "5") onFocusedBoxChange("history");
2996
4296
  if (input === "6") onFocusedBoxChange("viewer");
2997
4297
  },
2998
4298
  { isActive }
2999
4299
  );
3000
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", flexGrow: 1, children: [
3001
- /* @__PURE__ */ jsx15(
4300
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
4301
+ /* @__PURE__ */ jsx20(
3002
4302
  LogsHistoryBox,
3003
4303
  {
3004
4304
  logFiles: logs.logFiles,
@@ -3009,7 +4309,7 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
3009
4309
  isActive: isActive && focusedBox === "history"
3010
4310
  }
3011
4311
  ),
3012
- /* @__PURE__ */ jsx15(
4312
+ /* @__PURE__ */ jsx20(
3013
4313
  LogViewerBox,
3014
4314
  {
3015
4315
  date: logs.selectedDate,
@@ -3023,10 +4323,11 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
3023
4323
  }
3024
4324
 
3025
4325
  // src/components/ui/KeybindingsBar.tsx
3026
- import { Box as Box16, Text as Text14 } from "ink";
3027
- 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";
3028
4328
  var globalBindings = [
3029
4329
  { key: "1-4", label: "Focus" },
4330
+ { key: "Tab", label: "Switch Tab" },
3030
4331
  { key: "j/k", label: "Navigate" },
3031
4332
  { key: "Ctrl+C", label: "Quit" }
3032
4333
  ];
@@ -3034,18 +4335,24 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
3034
4335
  var DUCK_ASCII = "<(')___";
3035
4336
  function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
3036
4337
  const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
3037
- return /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, paddingX: 1, gap: 2, children: [
3038
- allBindings.map((binding) => /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
3039
- /* @__PURE__ */ jsx16(Text14, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
3040
- /* @__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 })
3041
4342
  ] }, binding.key)),
3042
- (duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
3043
- /* @__PURE__ */ jsx16(Text14, { children: DUCK_ASCII }),
3044
- /* @__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 })
3045
4346
  ] })
3046
4347
  ] });
3047
4348
  }
3048
4349
 
4350
+ // src/constants/tabs.ts
4351
+ var COLUMN2_TABS = [
4352
+ { id: "logs", label: "Logs" },
4353
+ { id: "jira-browser", label: "Jira" }
4354
+ ];
4355
+
3049
4356
  // src/constants/github.ts
3050
4357
  var GITHUB_KEYBINDINGS = {
3051
4358
  remotes: [{ key: "Space", label: "Select Remote" }],
@@ -3063,6 +4370,27 @@ var GITHUB_KEYBINDINGS = {
3063
4370
  ]
3064
4371
  };
3065
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
+
3066
4394
  // src/constants/jira.ts
3067
4395
  var JIRA_KEYBINDINGS = {
3068
4396
  not_configured: [{ key: "c", label: "Configure Jira" }],
@@ -3102,34 +4430,42 @@ function computeKeybindings(focusedView, state) {
3102
4430
  return JIRA_KEYBINDINGS[state.jira.jiraState];
3103
4431
  case "logs":
3104
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];
3105
4436
  default:
3106
4437
  return [];
3107
4438
  }
3108
4439
  }
3109
4440
 
3110
4441
  // src/app.tsx
3111
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
4442
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
3112
4443
  function App() {
3113
4444
  const { exit } = useApp();
3114
- const [focusedView, setFocusedView] = useState16("github");
3115
- const [modalOpen, setModalOpen] = useState16(false);
3116
- 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");
3117
4449
  const duck = useRubberDuck();
3118
- const [githubFocusedBox, setGithubFocusedBox] = useState16("remotes");
3119
- const [jiraState, setJiraState] = useState16("not_configured");
3120
- const [logsFocusedBox, setLogsFocusedBox] = useState16("history");
3121
- 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(
3122
4456
  () => computeKeybindings(focusedView, {
3123
4457
  github: { focusedBox: githubFocusedBox },
3124
4458
  jira: { jiraState, modalOpen },
3125
- logs: { focusedBox: logsFocusedBox }
4459
+ logs: { focusedBox: logsFocusedBox },
4460
+ "jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
3126
4461
  }),
3127
- [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox]
4462
+ [focusedView, githubFocusedBox, jiraState, modalOpen, logsFocusedBox, jiraBrowserFocusedBox, jiraBrowserModalOpen]
3128
4463
  );
3129
- const handleLogUpdated = useCallback9(() => {
4464
+ const handleLogUpdated = useCallback11(() => {
3130
4465
  setLogRefreshKey((prev) => prev + 1);
3131
4466
  }, []);
3132
- useInput13(
4467
+ const anyModalOpen = modalOpen || jiraBrowserModalOpen;
4468
+ useInput18(
3133
4469
  (input, key) => {
3134
4470
  if (key.ctrl && input === "c") {
3135
4471
  exit();
@@ -3141,12 +4477,23 @@ function App() {
3141
4477
  setFocusedView("jira");
3142
4478
  }
3143
4479
  if (input === "5") {
3144
- setFocusedView("logs");
3145
- setLogsFocusedBox("history");
4480
+ setFocusedView(activeTab);
4481
+ if (activeTab === "logs") setLogsFocusedBox("history");
4482
+ if (activeTab === "jira-browser") setJiraBrowserFocusedBox("saved-views");
3146
4483
  }
3147
4484
  if (input === "6") {
3148
- setFocusedView("logs");
3149
- 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
+ });
3150
4497
  }
3151
4498
  if (input === "d") {
3152
4499
  duck.toggleDuck();
@@ -3155,12 +4502,19 @@ function App() {
3155
4502
  duck.quack();
3156
4503
  }
3157
4504
  },
3158
- { isActive: !modalOpen }
4505
+ { isActive: !anyModalOpen }
3159
4506
  );
3160
- return /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
3161
- /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
3162
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
3163
- /* @__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(
3164
4518
  GitHubView,
3165
4519
  {
3166
4520
  isActive: focusedView === "github",
@@ -3168,7 +4522,7 @@ function App() {
3168
4522
  onLogUpdated: handleLogUpdated
3169
4523
  }
3170
4524
  ),
3171
- /* @__PURE__ */ jsx17(
4525
+ /* @__PURE__ */ jsx22(
3172
4526
  JiraView,
3173
4527
  {
3174
4528
  isActive: focusedView === "jira",
@@ -3178,21 +4532,33 @@ function App() {
3178
4532
  }
3179
4533
  )
3180
4534
  ] }),
3181
- /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsx17(
3182
- LogsView,
3183
- {
3184
- isActive: focusedView === "logs",
3185
- refreshKey: logRefreshKey,
3186
- focusedBox: logsFocusedBox,
3187
- onFocusedBoxChange: setLogsFocusedBox
3188
- }
3189
- ) })
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
+ ] })
3190
4556
  ] }),
3191
- /* @__PURE__ */ jsx17(
4557
+ /* @__PURE__ */ jsx22(
3192
4558
  KeybindingsBar,
3193
4559
  {
3194
4560
  contextBindings: keybindings,
3195
- modalOpen,
4561
+ modalOpen: anyModalOpen,
3196
4562
  duck: { visible: duck.visible, message: duck.message }
3197
4563
  }
3198
4564
  )
@@ -3203,31 +4569,31 @@ function App() {
3203
4569
  import { render as inkRender } from "ink";
3204
4570
 
3205
4571
  // src/lib/Screen.tsx
3206
- import { useCallback as useCallback10, useEffect as useEffect13, useState as useState17 } from "react";
3207
- import { Box as Box18, useStdout as useStdout2 } from "ink";
3208
- 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";
3209
4575
  function Screen({ children }) {
3210
4576
  const { stdout } = useStdout2();
3211
- const getSize = useCallback10(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
3212
- const [size, setSize] = useState17(getSize);
3213
- useEffect13(() => {
4577
+ const getSize = useCallback12(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
4578
+ const [size, setSize] = useState22(getSize);
4579
+ useEffect17(() => {
3214
4580
  const onResize = () => setSize(getSize());
3215
4581
  stdout.on("resize", onResize);
3216
4582
  return () => {
3217
4583
  stdout.off("resize", onResize);
3218
4584
  };
3219
4585
  }, [stdout, getSize]);
3220
- return /* @__PURE__ */ jsx18(Box18, { height: size.height, width: size.width, children });
4586
+ return /* @__PURE__ */ jsx23(Box23, { height: size.height, width: size.width, children });
3221
4587
  }
3222
4588
 
3223
4589
  // src/lib/render.tsx
3224
- import { jsx as jsx19 } from "react/jsx-runtime";
4590
+ import { jsx as jsx24 } from "react/jsx-runtime";
3225
4591
  var ENTER_ALT_BUFFER = "\x1B[?1049h";
3226
4592
  var EXIT_ALT_BUFFER = "\x1B[?1049l";
3227
4593
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
3228
4594
  function render(node, options) {
3229
4595
  process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
3230
- const element = /* @__PURE__ */ jsx19(Screen, { children: node });
4596
+ const element = /* @__PURE__ */ jsx24(Screen, { children: node });
3231
4597
  const instance = inkRender(element, options);
3232
4598
  setImmediate(() => instance.rerender(element));
3233
4599
  const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
@@ -3248,7 +4614,7 @@ function render(node, options) {
3248
4614
  }
3249
4615
 
3250
4616
  // src/cli.tsx
3251
- import { jsx as jsx20 } from "react/jsx-runtime";
4617
+ import { jsx as jsx25 } from "react/jsx-runtime";
3252
4618
  var cli = meow(
3253
4619
  `
3254
4620
  Usage
@@ -3281,4 +4647,4 @@ if (cli.flags.cwd) {
3281
4647
  process.exit(1);
3282
4648
  }
3283
4649
  }
3284
- render(/* @__PURE__ */ jsx20(App, {}));
4650
+ render(/* @__PURE__ */ jsx25(App, {}));