@arnavpisces/sutra 1.0.7 → 1.0.9

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 (91) hide show
  1. package/dist/api/jira-client.d.ts +33 -1
  2. package/dist/api/jira-client.d.ts.map +1 -1
  3. package/dist/api/jira-client.js +132 -23
  4. package/dist/api/jira-client.js.map +1 -1
  5. package/dist/app.d.ts.map +1 -1
  6. package/dist/app.js +93 -6
  7. package/dist/app.js.map +1 -1
  8. package/dist/components/common/EditableTextBox.d.ts.map +1 -1
  9. package/dist/components/common/EditableTextBox.js +58 -0
  10. package/dist/components/common/EditableTextBox.js.map +1 -1
  11. package/dist/components/common/FuzzySelect.d.ts +2 -1
  12. package/dist/components/common/FuzzySelect.d.ts.map +1 -1
  13. package/dist/components/common/FuzzySelect.js +6 -2
  14. package/dist/components/common/FuzzySelect.js.map +1 -1
  15. package/dist/components/common/SearchBar.js +1 -1
  16. package/dist/components/common/SearchBar.js.map +1 -1
  17. package/dist/components/common/TextEditor.js +1 -1
  18. package/dist/components/common/TextEditor.js.map +1 -1
  19. package/dist/components/common/ThemeModal.d.ts +9 -0
  20. package/dist/components/common/ThemeModal.d.ts.map +1 -0
  21. package/dist/components/common/ThemeModal.js +34 -0
  22. package/dist/components/common/ThemeModal.js.map +1 -0
  23. package/dist/components/common/WordTextInput.d.ts +17 -0
  24. package/dist/components/common/WordTextInput.d.ts.map +1 -0
  25. package/dist/components/common/WordTextInput.js +142 -0
  26. package/dist/components/common/WordTextInput.js.map +1 -0
  27. package/dist/components/confluence/PageAttachments.js +1 -1
  28. package/dist/components/confluence/PageAttachments.js.map +1 -1
  29. package/dist/components/confluence/PageBrowser.js +1 -1
  30. package/dist/components/confluence/PageBrowser.js.map +1 -1
  31. package/dist/components/confluence/PageEditor.js +1 -1
  32. package/dist/components/confluence/PageEditor.js.map +1 -1
  33. package/dist/components/confluence/PageLabels.js +1 -1
  34. package/dist/components/confluence/PageLabels.js.map +1 -1
  35. package/dist/components/jira/CommentForm.js +1 -1
  36. package/dist/components/jira/CommentForm.js.map +1 -1
  37. package/dist/components/jira/CreateTicket.d.ts +4 -1
  38. package/dist/components/jira/CreateTicket.d.ts.map +1 -1
  39. package/dist/components/jira/CreateTicket.js +28 -10
  40. package/dist/components/jira/CreateTicket.js.map +1 -1
  41. package/dist/components/jira/IssueList.d.ts.map +1 -1
  42. package/dist/components/jira/IssueList.js +117 -12
  43. package/dist/components/jira/IssueList.js.map +1 -1
  44. package/dist/components/jira/JiraIssueRow.d.ts.map +1 -1
  45. package/dist/components/jira/JiraIssueRow.js +4 -2
  46. package/dist/components/jira/JiraIssueRow.js.map +1 -1
  47. package/dist/components/jira/JiraView.d.ts +1 -1
  48. package/dist/components/jira/JiraView.d.ts.map +1 -1
  49. package/dist/components/jira/JiraView.js +115 -40
  50. package/dist/components/jira/JiraView.js.map +1 -1
  51. package/dist/components/jira/JqlSearch.d.ts.map +1 -1
  52. package/dist/components/jira/JqlSearch.js +50 -2
  53. package/dist/components/jira/JqlSearch.js.map +1 -1
  54. package/dist/components/jira/QuickFilters.d.ts +7 -2
  55. package/dist/components/jira/QuickFilters.d.ts.map +1 -1
  56. package/dist/components/jira/QuickFilters.js +121 -36
  57. package/dist/components/jira/QuickFilters.js.map +1 -1
  58. package/dist/components/jira/TicketDetail.d.ts +11 -3
  59. package/dist/components/jira/TicketDetail.d.ts.map +1 -1
  60. package/dist/components/jira/TicketDetail.js +594 -44
  61. package/dist/components/jira/TicketDetail.js.map +1 -1
  62. package/dist/components/jira/TicketEditor.js +1 -1
  63. package/dist/components/jira/TicketEditor.js.map +1 -1
  64. package/dist/components/jira/TicketList.d.ts.map +1 -1
  65. package/dist/components/jira/TicketList.js +159 -11
  66. package/dist/components/jira/TicketList.js.map +1 -1
  67. package/dist/config/config-manager.d.ts +2 -0
  68. package/dist/config/config-manager.d.ts.map +1 -1
  69. package/dist/config/config-manager.js +10 -0
  70. package/dist/config/config-manager.js.map +1 -1
  71. package/dist/config/types.d.ts +3 -0
  72. package/dist/config/types.d.ts.map +1 -1
  73. package/dist/config/types.js +1 -0
  74. package/dist/config/types.js.map +1 -1
  75. package/dist/index.js +22 -2
  76. package/dist/index.js.map +1 -1
  77. package/dist/theme/te.d.ts +12 -1
  78. package/dist/theme/te.d.ts.map +1 -1
  79. package/dist/theme/te.js +180 -12
  80. package/dist/theme/te.js.map +1 -1
  81. package/dist/utils/auto-updater.d.ts +14 -1
  82. package/dist/utils/auto-updater.d.ts.map +1 -1
  83. package/dist/utils/auto-updater.js +113 -22
  84. package/dist/utils/auto-updater.js.map +1 -1
  85. package/dist/utils/external-editor.d.ts.map +1 -1
  86. package/dist/utils/external-editor.js +24 -3
  87. package/dist/utils/external-editor.js.map +1 -1
  88. package/dist/utils/jira-colors.d.ts.map +1 -1
  89. package/dist/utils/jira-colors.js +21 -20
  90. package/dist/utils/jira-colors.js.map +1 -1
  91. package/package.json +2 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useEffect, useRef } from 'react';
3
3
  import { Box, Text, useInput, useStdout } from 'ink';
4
- import TextInput from 'ink-text-input';
4
+ import TextInput from '../common/WordTextInput.js';
5
5
  import { AdfConverter } from '../../formatters/adf-converter.js';
6
6
  import { SelectableItem } from '../common/SelectableItem.js';
7
7
  import { ShortcutHints } from '../common/ShortcutHints.js';
@@ -14,7 +14,215 @@ import { join } from 'path';
14
14
  import { highlightMarkdownLine, detectCodeBlock, highlightCodeLine } from '../../utils/markdown-highlighter.js';
15
15
  import { getJiraStatusColor, getJiraTypeColor, getJiraPriorityColor } from '../../utils/jira-colors.js';
16
16
  import { te } from '../../theme/te.js';
17
- export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [], priorities = [], onSaveTitle, onSaveDescription, onSavePriority, onAssignToMe, onAddComment, onUpdateComment, onTransition, onFetchComments, onDownloadAttachment, onUploadAttachment, onBookmarkChanged, onRefresh, onBack }) {
17
+ function getRequiredTransitionFields(transition) {
18
+ if (!transition?.fields)
19
+ return [];
20
+ return Object.entries(transition.fields)
21
+ .filter(([, definition]) => {
22
+ if (!definition?.required)
23
+ return false;
24
+ const operations = Array.isArray(definition.operations) ? definition.operations : [];
25
+ const canSet = operations.length === 0 || operations.includes('set');
26
+ return canSet;
27
+ })
28
+ .map(([fieldId, definition]) => ({
29
+ id: fieldId,
30
+ name: definition.name || fieldId,
31
+ definition,
32
+ }));
33
+ }
34
+ function buildFieldValueFromAllowed(fieldId, rawValue) {
35
+ if (!rawValue || typeof rawValue !== 'object') {
36
+ return rawValue;
37
+ }
38
+ const schemaType = String(rawValue?.schema?.type || '').toLowerCase();
39
+ if (schemaType === 'user' && rawValue.accountId) {
40
+ return { accountId: rawValue.accountId };
41
+ }
42
+ if (rawValue.id !== undefined && rawValue.id !== null && rawValue.id !== '') {
43
+ return { id: String(rawValue.id) };
44
+ }
45
+ if (rawValue.value !== undefined) {
46
+ return { value: rawValue.value };
47
+ }
48
+ if (rawValue.name !== undefined) {
49
+ return { name: rawValue.name };
50
+ }
51
+ if (rawValue.key !== undefined) {
52
+ return { key: rawValue.key };
53
+ }
54
+ // Fallback to full object for custom transitions expecting richer payloads.
55
+ return rawValue;
56
+ }
57
+ function formatAllowedValueLabel(rawValue) {
58
+ if (!rawValue || typeof rawValue !== 'object') {
59
+ return String(rawValue ?? '');
60
+ }
61
+ if (typeof rawValue.displayName === 'string' && rawValue.displayName.trim()) {
62
+ return rawValue.displayName;
63
+ }
64
+ if (typeof rawValue.name === 'string' && rawValue.name.trim()) {
65
+ return rawValue.name;
66
+ }
67
+ if (typeof rawValue.value === 'string' && rawValue.value.trim()) {
68
+ return rawValue.value;
69
+ }
70
+ if (typeof rawValue.key === 'string' && rawValue.key.trim()) {
71
+ return rawValue.key;
72
+ }
73
+ if (rawValue.id !== undefined && rawValue.id !== null) {
74
+ return String(rawValue.id);
75
+ }
76
+ return JSON.stringify(rawValue);
77
+ }
78
+ function buildFieldValueFromText(fieldId, definition, rawInput) {
79
+ const input = rawInput.trim();
80
+ const schemaType = String(definition?.schema?.type || '').toLowerCase();
81
+ const schemaSystem = String(definition?.schema?.system || '').toLowerCase();
82
+ const schemaCustom = String(definition?.schema?.custom || '').toLowerCase();
83
+ const requiresAdf = schemaType === 'doc' ||
84
+ schemaType === 'textarea' ||
85
+ schemaSystem === 'description' ||
86
+ schemaCustom.includes(':textarea') ||
87
+ schemaCustom.includes('richtext');
88
+ const asAdfDoc = (text) => ({
89
+ type: 'doc',
90
+ version: 1,
91
+ content: [
92
+ {
93
+ type: 'paragraph',
94
+ content: [
95
+ {
96
+ type: 'text',
97
+ text,
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ });
103
+ if (schemaType === 'number') {
104
+ const parsed = Number(input);
105
+ return Number.isFinite(parsed) ? parsed : input;
106
+ }
107
+ if (schemaType === 'user') {
108
+ return { accountId: input };
109
+ }
110
+ if (schemaType === 'option' ||
111
+ schemaType === 'priority' ||
112
+ schemaType === 'resolution' ||
113
+ schemaType === 'version' ||
114
+ schemaType === 'component' ||
115
+ schemaSystem === 'resolution' ||
116
+ schemaSystem === 'priority') {
117
+ return /^\d+$/.test(input) ? { id: input } : { name: input };
118
+ }
119
+ if (schemaType === 'array') {
120
+ const values = input
121
+ .split(',')
122
+ .map((part) => part.trim())
123
+ .filter(Boolean);
124
+ const itemType = String(definition?.schema?.items || '').toLowerCase();
125
+ if (itemType === 'number') {
126
+ return values.map((v) => {
127
+ const parsed = Number(v);
128
+ return Number.isFinite(parsed) ? parsed : v;
129
+ });
130
+ }
131
+ if (itemType === 'option' || itemType === 'version' || itemType === 'component') {
132
+ return values.map((v) => (/^\d+$/.test(v) ? { id: v } : { name: v }));
133
+ }
134
+ if (itemType === 'user') {
135
+ return values.map((v) => ({ accountId: v }));
136
+ }
137
+ return values;
138
+ }
139
+ if (requiresAdf) {
140
+ return asAdfDoc(input);
141
+ }
142
+ // URL and string-like custom fields use raw string.
143
+ if (schemaType === 'string' || schemaCustom.includes('url')) {
144
+ return input;
145
+ }
146
+ return input;
147
+ }
148
+ function getTransitionFieldInputHint(definition) {
149
+ const schemaType = String(definition?.schema?.type || '').toLowerCase();
150
+ const schemaCustom = String(definition?.schema?.custom || '').toLowerCase();
151
+ if ((definition.allowedValues || []).length > 0) {
152
+ return 'Pick a value with ↑/↓ and press Enter.';
153
+ }
154
+ if (schemaCustom.includes('url')) {
155
+ return 'Enter a full URL (for example https://example.com/doc).';
156
+ }
157
+ if (schemaType === 'number') {
158
+ return 'Enter a numeric value.';
159
+ }
160
+ if (schemaType === 'array') {
161
+ return 'Enter comma-separated values.';
162
+ }
163
+ if (schemaType === 'user') {
164
+ return 'Type a Jira user name and select from suggestions (or enter accountId).';
165
+ }
166
+ return 'Enter a value and press Enter.';
167
+ }
168
+ function normalizeFieldToken(value) {
169
+ return value.trim().toLowerCase();
170
+ }
171
+ function extractRequiredFieldNamesFromError(errorMessage) {
172
+ const names = new Set();
173
+ const trimmed = errorMessage.trim();
174
+ const payloadStart = trimmed.indexOf('{');
175
+ const payload = payloadStart >= 0 ? trimmed.slice(payloadStart) : '';
176
+ let errorMessages = [];
177
+ let errorEntries = [];
178
+ if (payload) {
179
+ try {
180
+ const parsed = JSON.parse(payload);
181
+ if (Array.isArray(parsed.errorMessages)) {
182
+ errorMessages = parsed.errorMessages.filter((entry) => typeof entry === 'string');
183
+ }
184
+ if (parsed.errors && typeof parsed.errors === 'object') {
185
+ errorEntries = Object.entries(parsed.errors).filter((entry) => typeof entry[1] === 'string');
186
+ }
187
+ }
188
+ catch {
189
+ // Ignore parse issues and fallback to regex scanning on plain text.
190
+ }
191
+ }
192
+ const combinedMessages = [...errorMessages];
193
+ if (combinedMessages.length === 0) {
194
+ combinedMessages.push(trimmed);
195
+ }
196
+ for (const message of combinedMessages) {
197
+ const match = message.match(/field\s+(.+?)\s+is required/i);
198
+ if (match && match[1]) {
199
+ names.add(match[1].trim());
200
+ }
201
+ }
202
+ for (const [fieldKey, message] of errorEntries) {
203
+ if (/required/i.test(message)) {
204
+ names.add(fieldKey);
205
+ }
206
+ }
207
+ return [...names];
208
+ }
209
+ function buildQueueForNamedRequiredFields(fields, requiredNames) {
210
+ const wanted = new Set(requiredNames.map(normalizeFieldToken));
211
+ const queue = [];
212
+ for (const [fieldId, definition] of Object.entries(fields || {})) {
213
+ const nameToken = normalizeFieldToken(definition?.name || '');
214
+ const idToken = normalizeFieldToken(fieldId);
215
+ if (!wanted.has(nameToken) && !wanted.has(idToken))
216
+ continue;
217
+ queue.push({
218
+ id: fieldId,
219
+ name: definition.name || fieldId,
220
+ definition,
221
+ });
222
+ }
223
+ return queue;
224
+ }
225
+ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [], priorities = [], onSaveTitle, onSaveDescription, onSavePriority, onAssignToMe, onAddComment, onUpdateComment, onTransition, onRefreshTransitions, onSearchUsers, onResolveTransitionFields, onFetchComments, onDownloadAttachment, onUploadAttachment, onBookmarkChanged, onCreateChildTicket, onRefresh, onBack }) {
18
226
  const { stdout } = useStdout();
19
227
  const [selectedIndex, setSelectedIndex] = useState(0);
20
228
  const [mode, setMode] = useState('view');
@@ -22,6 +230,16 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
22
230
  const [editingCommentId, setEditingCommentId] = useState(null);
23
231
  const [selectedCommentIndex, setSelectedCommentIndex] = useState(0);
24
232
  const [selectedTransitionIndex, setSelectedTransitionIndex] = useState(0);
233
+ const [transitionFieldsQueue, setTransitionFieldsQueue] = useState([]);
234
+ const [transitionFieldCursor, setTransitionFieldCursor] = useState(0);
235
+ const [transitionFieldValues, setTransitionFieldValues] = useState({});
236
+ const [transitionFieldInput, setTransitionFieldInput] = useState('');
237
+ const [transitionAllowedIndex, setTransitionAllowedIndex] = useState(0);
238
+ const [transitionUserOptions, setTransitionUserOptions] = useState([]);
239
+ const [transitionUserIndex, setTransitionUserIndex] = useState(0);
240
+ const [transitionUserLoading, setTransitionUserLoading] = useState(false);
241
+ const [pendingTransitionId, setPendingTransitionId] = useState(null);
242
+ const [transitionLoading, setTransitionLoading] = useState(false);
25
243
  const [selectedPriorityIndex, setSelectedPriorityIndex] = useState(0);
26
244
  const [saving, setSaving] = useState(false);
27
245
  const [error, setError] = useState(null);
@@ -31,6 +249,10 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
31
249
  const [allComments, setAllComments] = useState([]);
32
250
  const [attachmentsMessage, setAttachmentsMessage] = useState(null);
33
251
  const [uploadPath, setUploadPath] = useState('');
252
+ const [isExternalEditing, setIsExternalEditing] = useState(false);
253
+ const [ignoreEscapeUntil, setIgnoreEscapeUntil] = useState(0);
254
+ const externalEditorBusyRef = useRef(false);
255
+ const userSearchCacheRef = useRef(new Map());
34
256
  const description = AdfConverter.adfToMarkdown(issue.fields.description);
35
257
  const parentIssue = issue.fields.parent;
36
258
  const parentKey = parentIssue?.key || '';
@@ -40,10 +262,79 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
40
262
  const assigneeIsMe = Boolean(currentAccountId && currentAssigneeAccountId === currentAccountId);
41
263
  const comments = issue.fields.comment?.comments || [];
42
264
  const attachments = issue.fields.attachment || [];
265
+ const isEpicIssue = String(issue.fields.issuetype?.name || '').trim().toLowerCase() === 'epic';
43
266
  const terminalRows = stdout?.rows || 24;
44
267
  const compactViewport = terminalRows <= 32;
45
268
  const previewWidth = Math.max(20, (stdout?.columns || 80) - 10);
46
269
  const descriptionPreviewLines = compactViewport ? 3 : 5;
270
+ const activeTransition = pendingTransitionId
271
+ ? transitions.find((transition) => transition.id === pendingTransitionId)
272
+ : null;
273
+ const currentTransitionField = transitionFieldsQueue[transitionFieldCursor];
274
+ const currentTransitionAllowedValues = Array.isArray(currentTransitionField?.definition?.allowedValues)
275
+ ? currentTransitionField.definition.allowedValues
276
+ : [];
277
+ const isCurrentTransitionFieldSelect = currentTransitionAllowedValues.length > 0;
278
+ const currentTransitionSchemaType = String(currentTransitionField?.definition?.schema?.type || '').toLowerCase();
279
+ const currentTransitionSchemaItems = String(currentTransitionField?.definition?.schema?.items || '').toLowerCase();
280
+ const isCurrentTransitionUserField = !isCurrentTransitionFieldSelect && (currentTransitionSchemaType === 'user' ||
281
+ (currentTransitionSchemaType === 'array' && currentTransitionSchemaItems === 'user'));
282
+ useEffect(() => {
283
+ if (selectedTransitionIndex < transitions.length)
284
+ return;
285
+ setSelectedTransitionIndex(Math.max(0, transitions.length - 1));
286
+ }, [selectedTransitionIndex, transitions.length]);
287
+ useEffect(() => {
288
+ if (mode !== 'transition-fields' || !isCurrentTransitionUserField || !onSearchUsers) {
289
+ setTransitionUserLoading(false);
290
+ setTransitionUserOptions([]);
291
+ setTransitionUserIndex(0);
292
+ return;
293
+ }
294
+ const query = transitionFieldInput.trim();
295
+ if (!query) {
296
+ setTransitionUserLoading(false);
297
+ setTransitionUserOptions([]);
298
+ setTransitionUserIndex(0);
299
+ return;
300
+ }
301
+ const cacheKey = query.toLowerCase();
302
+ const cached = userSearchCacheRef.current.get(cacheKey);
303
+ if (cached) {
304
+ setTransitionUserOptions(cached);
305
+ setTransitionUserIndex(0);
306
+ setTransitionUserLoading(false);
307
+ return;
308
+ }
309
+ let cancelled = false;
310
+ const timer = setTimeout(async () => {
311
+ setTransitionUserLoading(true);
312
+ try {
313
+ const users = await onSearchUsers(query);
314
+ if (cancelled)
315
+ return;
316
+ const normalized = (users || []).slice(0, 8);
317
+ userSearchCacheRef.current.set(cacheKey, normalized);
318
+ setTransitionUserOptions(normalized);
319
+ setTransitionUserIndex(0);
320
+ }
321
+ catch {
322
+ if (!cancelled) {
323
+ setTransitionUserOptions([]);
324
+ setTransitionUserIndex(0);
325
+ }
326
+ }
327
+ finally {
328
+ if (!cancelled) {
329
+ setTransitionUserLoading(false);
330
+ }
331
+ }
332
+ }, 180);
333
+ return () => {
334
+ cancelled = true;
335
+ clearTimeout(timer);
336
+ };
337
+ }, [mode, isCurrentTransitionUserField, onSearchUsers, transitionFieldInput, currentTransitionField?.id]);
47
338
  const renderMarkdownPreview = (markdown, maxLines) => {
48
339
  const lines = markdown.split('\n');
49
340
  return lines.slice(0, maxLines).map((line, i) => {
@@ -58,7 +349,11 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
58
349
  // Get user's own comments that can be edited
59
350
  const myComments = comments.filter((c) => currentAccountId && c.author?.accountId === currentAccountId);
60
351
  // Selectable items in view mode.
61
- const selectableItems = ['status', 'assignee', 'priority', 'title', 'description', 'comments', 'attachments', 'add-comment'];
352
+ const selectableItems = ['status', 'assignee', 'priority', 'title'];
353
+ if (isEpicIssue) {
354
+ selectableItems.push('add-child-ticket');
355
+ }
356
+ selectableItems.push('description', 'comments', 'attachments', 'add-comment');
62
357
  if (myComments.length > 0) {
63
358
  selectableItems.push('edit-comment');
64
359
  }
@@ -87,22 +382,46 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
87
382
  .finally(() => setCommentsLoading(false));
88
383
  }
89
384
  }, [mode, onFetchComments]);
385
+ useEffect(() => {
386
+ setPendingTransitionId(null);
387
+ setTransitionFieldsQueue([]);
388
+ setTransitionFieldCursor(0);
389
+ setTransitionFieldValues({});
390
+ setTransitionFieldInput('');
391
+ setTransitionAllowedIndex(0);
392
+ }, [issue.key]);
90
393
  // Handle keyboard input
91
394
  useInput((input, key) => {
395
+ if (Date.now() < ignoreEscapeUntil) {
396
+ return;
397
+ }
92
398
  // External editor for long-form fields
93
399
  if ((key.ctrl && input === 'e') && (mode === 'edit-description' || mode === 'add-comment' || mode === 'edit-comment')) {
94
- (async () => {
95
- setSaving(true);
96
- const result = await openExternalEditor({
97
- content: editValue,
98
- extension: 'md',
99
- });
100
- setSaving(false);
101
- if (result.success && result.content !== undefined) {
102
- setEditValue(result.content);
400
+ if (externalEditorBusyRef.current || isExternalEditing) {
401
+ return;
402
+ }
403
+ externalEditorBusyRef.current = true;
404
+ setIsExternalEditing(true);
405
+ setSaving(true);
406
+ void (async () => {
407
+ try {
408
+ const result = await openExternalEditor({
409
+ content: editValue,
410
+ extension: 'md',
411
+ });
412
+ if (result.success && result.content !== undefined) {
413
+ setEditValue(result.content);
414
+ }
415
+ else if (result.error) {
416
+ setError(result.error);
417
+ }
103
418
  }
104
- else if (result.error) {
105
- setError(result.error);
419
+ finally {
420
+ setSaving(false);
421
+ setIsExternalEditing(false);
422
+ externalEditorBusyRef.current = false;
423
+ // Ignore trailing ESC emitted by editor quit sequences.
424
+ setIgnoreEscapeUntil(Date.now() + 300);
106
425
  }
107
426
  })();
108
427
  return;
@@ -124,6 +443,12 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
124
443
  }
125
444
  // Global Escape Handler
126
445
  if (key.escape) {
446
+ if (mode === 'transition-fields') {
447
+ setMode('select-status');
448
+ resetTransitionFieldFlow();
449
+ setError(null);
450
+ return;
451
+ }
127
452
  if (mode !== 'view') {
128
453
  handleCancel();
129
454
  }
@@ -196,6 +521,9 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
196
521
  }
197
522
  // Status selection mode
198
523
  if (mode === 'select-status') {
524
+ if (transitionLoading) {
525
+ return;
526
+ }
199
527
  if (key.upArrow) {
200
528
  setSelectedTransitionIndex(prev => Math.max(0, prev - 1));
201
529
  }
@@ -203,7 +531,29 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
203
531
  setSelectedTransitionIndex(prev => Math.min(transitions.length - 1, prev + 1));
204
532
  }
205
533
  else if (key.return && transitions.length > 0) {
206
- handleTransition();
534
+ void beginTransition();
535
+ }
536
+ return;
537
+ }
538
+ if (mode === 'transition-fields') {
539
+ if (isCurrentTransitionFieldSelect) {
540
+ if (key.upArrow) {
541
+ setTransitionAllowedIndex((prev) => Math.max(0, prev - 1));
542
+ }
543
+ else if (key.downArrow) {
544
+ setTransitionAllowedIndex((prev) => Math.min(currentTransitionAllowedValues.length - 1, prev + 1));
545
+ }
546
+ else if (key.return) {
547
+ submitCurrentTransitionAllowedField();
548
+ }
549
+ }
550
+ else if (isCurrentTransitionUserField && transitionUserOptions.length > 0) {
551
+ if (key.upArrow) {
552
+ setTransitionUserIndex((prev) => Math.max(0, prev - 1));
553
+ }
554
+ else if (key.downArrow) {
555
+ setTransitionUserIndex((prev) => Math.min(transitionUserOptions.length - 1, prev + 1));
556
+ }
207
557
  }
208
558
  return;
209
559
  }
@@ -237,7 +587,7 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
237
587
  else if (key.return) {
238
588
  handleSelect();
239
589
  }
240
- });
590
+ }, { isActive: !isExternalEditing });
241
591
  const handleCopyUrl = async () => {
242
592
  try {
243
593
  // Remove trailing slash from baseUrl to prevent double slashes
@@ -258,15 +608,209 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
258
608
  setCopyMessage('✗ Copy failed');
259
609
  }
260
610
  };
611
+ function resetTransitionFieldFlow() {
612
+ setPendingTransitionId(null);
613
+ setTransitionFieldsQueue([]);
614
+ setTransitionFieldCursor(0);
615
+ setTransitionFieldValues({});
616
+ setTransitionFieldInput('');
617
+ setTransitionAllowedIndex(0);
618
+ setTransitionUserLoading(false);
619
+ setTransitionUserOptions([]);
620
+ setTransitionUserIndex(0);
621
+ }
622
+ async function applyTransition(transitionId, fields) {
623
+ if (saving || !transitionId)
624
+ return;
625
+ setSaving(true);
626
+ setError(null);
627
+ try {
628
+ if (onTransition) {
629
+ await onTransition(transitionId, fields);
630
+ }
631
+ if (onRefresh)
632
+ await onRefresh();
633
+ if (onRefreshTransitions)
634
+ await onRefreshTransitions();
635
+ setMode('view');
636
+ resetTransitionFieldFlow();
637
+ }
638
+ catch (err) {
639
+ const errorMessage = err instanceof Error ? err.message : 'Status change failed';
640
+ const requiredFieldNames = extractRequiredFieldNamesFromError(errorMessage);
641
+ if (requiredFieldNames.length > 0 && onResolveTransitionFields) {
642
+ try {
643
+ const resolvedFields = await onResolveTransitionFields(transitionId, requiredFieldNames);
644
+ const queue = buildQueueForNamedRequiredFields(resolvedFields, requiredFieldNames);
645
+ if (queue.length > 0) {
646
+ const firstField = queue[0];
647
+ setPendingTransitionId(transitionId);
648
+ setTransitionFieldsQueue(queue);
649
+ setTransitionFieldCursor(0);
650
+ setTransitionFieldValues(fields || {});
651
+ setTransitionAllowedIndex(0);
652
+ if (Array.isArray(firstField?.definition?.allowedValues) &&
653
+ firstField.definition.allowedValues.length > 0) {
654
+ setTransitionFieldInput('');
655
+ }
656
+ else if (typeof firstField?.definition?.defaultValue === 'string') {
657
+ setTransitionFieldInput(firstField.definition.defaultValue);
658
+ }
659
+ else {
660
+ setTransitionFieldInput('');
661
+ }
662
+ setMode('transition-fields');
663
+ setError(`Fill required field${queue.length > 1 ? 's' : ''} to continue.`);
664
+ return;
665
+ }
666
+ }
667
+ catch {
668
+ // Fall through to existing error handling.
669
+ }
670
+ }
671
+ if (onRefreshTransitions) {
672
+ try {
673
+ await onRefreshTransitions();
674
+ }
675
+ catch {
676
+ // Keep showing the original transition error if refresh fails.
677
+ }
678
+ }
679
+ setError(errorMessage);
680
+ setMode('select-status');
681
+ resetTransitionFieldFlow();
682
+ }
683
+ finally {
684
+ setSaving(false);
685
+ }
686
+ }
687
+ async function beginTransition() {
688
+ if (transitionLoading)
689
+ return;
690
+ const selectedTransition = transitions[selectedTransitionIndex];
691
+ if (!selectedTransition)
692
+ return;
693
+ let transitionWithFields = selectedTransition;
694
+ if (onResolveTransitionFields) {
695
+ try {
696
+ const resolvedFields = await onResolveTransitionFields(selectedTransition.id);
697
+ if (Object.keys(resolvedFields).length > 0) {
698
+ transitionWithFields = {
699
+ ...selectedTransition,
700
+ fields: resolvedFields,
701
+ };
702
+ }
703
+ }
704
+ catch {
705
+ // Ignore metadata fetch issues and fallback to current transition payload.
706
+ }
707
+ }
708
+ const requiredFields = getRequiredTransitionFields(transitionWithFields);
709
+ if (requiredFields.length === 0) {
710
+ await applyTransition(selectedTransition.id);
711
+ return;
712
+ }
713
+ setPendingTransitionId(transitionWithFields.id);
714
+ setTransitionFieldsQueue(requiredFields);
715
+ setTransitionFieldCursor(0);
716
+ setTransitionFieldValues({});
717
+ setTransitionAllowedIndex(0);
718
+ if (Array.isArray(requiredFields[0]?.definition?.allowedValues) &&
719
+ requiredFields[0].definition.allowedValues.length > 0) {
720
+ setTransitionFieldInput('');
721
+ }
722
+ else if (typeof requiredFields[0]?.definition?.defaultValue === 'string') {
723
+ setTransitionFieldInput(requiredFields[0].definition.defaultValue);
724
+ }
725
+ else {
726
+ setTransitionFieldInput('');
727
+ }
728
+ setMode('transition-fields');
729
+ }
730
+ async function openStatusSelector() {
731
+ setError(null);
732
+ resetTransitionFieldFlow();
733
+ if (onRefreshTransitions) {
734
+ setTransitionLoading(true);
735
+ try {
736
+ await onRefreshTransitions();
737
+ }
738
+ catch (err) {
739
+ setError(err instanceof Error ? err.message : 'Failed to refresh available transitions');
740
+ }
741
+ finally {
742
+ setTransitionLoading(false);
743
+ }
744
+ }
745
+ setSelectedTransitionIndex(0);
746
+ setMode('select-status');
747
+ }
748
+ function commitCurrentTransitionField(value) {
749
+ const activeField = currentTransitionField;
750
+ if (!activeField || !pendingTransitionId)
751
+ return;
752
+ const nextValues = {
753
+ ...transitionFieldValues,
754
+ [activeField.id]: value,
755
+ };
756
+ setTransitionFieldValues(nextValues);
757
+ setError(null);
758
+ if (transitionFieldCursor >= transitionFieldsQueue.length - 1) {
759
+ void applyTransition(pendingTransitionId, nextValues);
760
+ return;
761
+ }
762
+ const nextCursor = transitionFieldCursor + 1;
763
+ const nextField = transitionFieldsQueue[nextCursor];
764
+ setTransitionFieldCursor(nextCursor);
765
+ setTransitionAllowedIndex(0);
766
+ if (Array.isArray(nextField?.definition?.allowedValues) && nextField.definition.allowedValues.length > 0) {
767
+ setTransitionFieldInput('');
768
+ }
769
+ else if (typeof nextField?.definition?.defaultValue === 'string') {
770
+ setTransitionFieldInput(nextField.definition.defaultValue);
771
+ }
772
+ else {
773
+ setTransitionFieldInput('');
774
+ }
775
+ }
776
+ function submitCurrentTransitionTextField() {
777
+ if (!currentTransitionField)
778
+ return;
779
+ const rawInput = transitionFieldInput.trim();
780
+ if (!rawInput) {
781
+ setError(`${currentTransitionField.name} is required.`);
782
+ return;
783
+ }
784
+ const parsed = buildFieldValueFromText(currentTransitionField.id, currentTransitionField.definition, rawInput);
785
+ commitCurrentTransitionField(parsed);
786
+ }
787
+ function submitCurrentTransitionInput() {
788
+ if (isCurrentTransitionUserField && transitionUserOptions.length > 0) {
789
+ const selected = transitionUserOptions[transitionUserIndex] || transitionUserOptions[0];
790
+ if (selected?.accountId) {
791
+ commitCurrentTransitionField({ accountId: selected.accountId });
792
+ return;
793
+ }
794
+ }
795
+ submitCurrentTransitionTextField();
796
+ }
797
+ function submitCurrentTransitionAllowedField() {
798
+ if (!currentTransitionField || currentTransitionAllowedValues.length === 0)
799
+ return;
800
+ const selected = currentTransitionAllowedValues[transitionAllowedIndex];
801
+ if (!selected) {
802
+ setError(`${currentTransitionField.name} is required.`);
803
+ return;
804
+ }
805
+ const parsed = buildFieldValueFromAllowed(currentTransitionField.id, selected);
806
+ commitCurrentTransitionField(parsed);
807
+ }
261
808
  const handleSelect = () => {
262
809
  setError(null);
263
810
  const item = selectableItems[selectedIndex];
264
811
  switch (item) {
265
812
  case 'status':
266
- if (transitions.length > 0) {
267
- setSelectedTransitionIndex(0);
268
- setMode('select-status');
269
- }
813
+ void openStatusSelector();
270
814
  break;
271
815
  case 'priority':
272
816
  if (priorities.length > 0) {
@@ -286,6 +830,9 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
286
830
  setEditValue(issue.fields.summary);
287
831
  setMode('edit-title');
288
832
  break;
833
+ case 'add-child-ticket':
834
+ onCreateChildTicket?.();
835
+ break;
289
836
  case 'description':
290
837
  setEditValue(description.slice(0, 500));
291
838
  setMode('edit-description');
@@ -310,26 +857,6 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
310
857
  break;
311
858
  }
312
859
  };
313
- const handleTransition = async () => {
314
- if (saving || transitions.length === 0)
315
- return;
316
- setSaving(true);
317
- setError(null);
318
- try {
319
- if (onTransition) {
320
- await onTransition(transitions[selectedTransitionIndex].id);
321
- }
322
- if (onRefresh)
323
- await onRefresh();
324
- setMode('view');
325
- }
326
- catch (err) {
327
- setError(err instanceof Error ? err.message : 'Status change failed');
328
- }
329
- finally {
330
- setSaving(false);
331
- }
332
- };
333
860
  const handlePriorityUpdate = async () => {
334
861
  if (saving || priorities.length === 0 || !onSavePriority)
335
862
  return;
@@ -416,10 +943,12 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
416
943
  };
417
944
  const handleCancel = () => {
418
945
  setMode('view');
946
+ setTransitionLoading(false);
419
947
  setEditValue('');
420
948
  setEditingCommentId(null);
421
949
  setError(null);
422
950
  setSelectedIndex(0);
951
+ resetTransitionFieldFlow();
423
952
  };
424
953
  const handleDownloadAttachment = async (attachment) => {
425
954
  if (!onDownloadAttachment)
@@ -438,12 +967,33 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
438
967
  };
439
968
  // Render status selection mode
440
969
  if (mode === 'select-status') {
441
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsxs(Text, { bold: true, color: te.accentAlt, children: ["\uD83D\uDCCB Change Status (Current: ", issue.fields.status.name, ")"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: transitions.map((t, i) => (_jsxs(Text, { color: i === selectedTransitionIndex ? te.accentAlt : te.fg, bold: i === selectedTransitionIndex, children: [i === selectedTransitionIndex ? '▶ ' : ' ', t.name, " \u2192 ", t.to.name] }, t.id))) })] }), saving && _jsx(Text, { dimColor: true, children: "Updating status..." }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
970
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsxs(Text, { bold: true, color: te.accentAlt, children: ["\uD83D\uDCCB Change Status (Current: ", issue.fields.status.name, ")"] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [transitionLoading && _jsx(Text, { color: te.muted, children: "Refreshing available transitions..." }), !transitionLoading && transitions.length === 0 && (_jsx(Text, { color: te.warning, children: "No status transitions are available from the current state." })), !transitionLoading && transitions.map((t, i) => {
971
+ const requiredCount = getRequiredTransitionFields(t).length;
972
+ return (_jsxs(Text, { color: i === selectedTransitionIndex ? te.accentAlt : te.fg, bold: i === selectedTransitionIndex, children: [i === selectedTransitionIndex ? '▶ ' : ' ', t.name, " \u2192 ", t.to.name, requiredCount > 0 ? ` (${requiredCount} required field${requiredCount > 1 ? 's' : ''})` : ''] }, t.id));
973
+ })] })] }), saving && _jsx(Text, { dimColor: true, children: "Updating status..." }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
442
974
  { key: '↑/↓', label: 'Navigate' },
443
975
  { key: 'Enter', label: 'Apply' },
444
976
  { key: 'Escape', label: 'Cancel' },
445
977
  ] }) })] }));
446
978
  }
979
+ if (mode === 'transition-fields') {
980
+ if (!activeTransition || !currentTransitionField) {
981
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsx(Text, { color: te.warning, children: "Missing transition field metadata. Press Escape and retry." })] }));
982
+ }
983
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsxs(Text, { bold: true, color: te.accentAlt, children: ["\uD83D\uDCCB Required Field (", transitionFieldCursor + 1, "/", transitionFieldsQueue.length, ")"] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "white", children: currentTransitionField.name }), _jsx(Text, { dimColor: true, children: getTransitionFieldInputHint(currentTransitionField.definition) })] }), isCurrentTransitionFieldSelect ? (_jsx(Box, { marginTop: 1, flexDirection: "column", children: currentTransitionAllowedValues.map((option, idx) => (_jsxs(Text, { color: idx === transitionAllowedIndex ? te.accentAlt : te.fg, bold: idx === transitionAllowedIndex, children: [idx === transitionAllowedIndex ? '▶ ' : ' ', formatAllowedValueLabel(option)] }, `${currentTransitionField.id}-${idx}`))) })) : (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: te.accent, paddingX: 1, children: _jsx(TextInput, { value: transitionFieldInput, onChange: setTransitionFieldInput, onSubmit: submitCurrentTransitionInput, placeholder: `Enter ${currentTransitionField.name}...`, focus: !saving && !isExternalEditing }) })), !isCurrentTransitionFieldSelect && isCurrentTransitionUserField && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [transitionUserLoading && _jsx(Text, { color: te.muted, children: "Searching users..." }), !transitionUserLoading && transitionUserOptions.length > 0 && transitionUserOptions.map((user, idx) => (_jsxs(Text, { color: idx === transitionUserIndex ? te.accentAlt : te.fg, bold: idx === transitionUserIndex, children: [idx === transitionUserIndex ? '▶ ' : ' ', user.displayName, user.emailAddress ? ` (${user.emailAddress})` : ''] }, `${currentTransitionField.id}:${user.accountId}`))), !transitionUserLoading && transitionFieldInput.trim().length > 0 && transitionUserOptions.length === 0 && (_jsx(Text, { color: te.muted, children: "No matching users found. Press Enter to use the typed value." }))] }))] }), saving && _jsx(Text, { dimColor: true, children: "Updating status..." }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: isCurrentTransitionFieldSelect
984
+ ? [
985
+ { key: '↑/↓', label: 'Choose' },
986
+ { key: 'Enter', label: 'Next' },
987
+ { key: 'Escape', label: 'Cancel' },
988
+ ]
989
+ : [
990
+ ...(isCurrentTransitionUserField
991
+ ? [{ key: '↑/↓', label: 'User' }]
992
+ : []),
993
+ { key: 'Enter', label: 'Next' },
994
+ { key: 'Escape', label: 'Cancel' },
995
+ ] }) })] }));
996
+ }
447
997
  // Render comment selection mode
448
998
  if (mode === 'select-comment') {
449
999
  return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsx(Text, { bold: true, color: te.accentAlt, children: "Select Comment to Edit:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: myComments.map((c, i) => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsxs(Text, { color: i === selectedCommentIndex ? te.accentAlt : te.fg, children: [i === selectedCommentIndex ? '▶ ' : ' ', c.created.split('T')[0], " - ", AdfConverter.adfToMarkdown(c.body).slice(0, 50), "..."] }) }, c.id || i))) })] }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
@@ -508,14 +1058,14 @@ export function TicketDetail({ issue, baseUrl, currentAccountId, transitions = [
508
1058
  'add-comment': 'Add Comment',
509
1059
  'edit-comment': 'Edit Comment',
510
1060
  };
511
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsxs(Text, { bold: true, color: te.accentAlt, children: ["\u270E ", modeLabels[mode]] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: editValue, onChange: setEditValue, onSubmit: handleSave }) })] }), saving && _jsx(Text, { dimColor: true, children: "Saving..." }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
1061
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: te.accent, paddingX: 1, marginY: 1, children: [_jsxs(Text, { bold: true, color: te.accentAlt, children: ["\u270E ", modeLabels[mode]] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: editValue, onChange: setEditValue, onSubmit: handleSave, focus: !saving && !isExternalEditing }) })] }), saving && _jsx(Text, { dimColor: true, children: "Saving..." }), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
512
1062
  { key: 'Enter', label: 'Save' },
513
1063
  { key: 'Ctrl+E', label: '$EDITOR' },
514
1064
  { key: 'Escape', label: 'Cancel' },
515
1065
  ] }) })] }));
516
1066
  }
517
1067
  // Render view mode with selectable items
518
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), copyMessage && _jsx(Text, { color: "green", children: copyMessage }), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: "white", children: issue.key }), _jsxs(Text, { color: getJiraStatusColor(issue.fields.status?.name), children: [" ", issue.fields.status?.name] }), issue.fields.issuetype?.name && (_jsxs(Text, { color: getJiraTypeColor(issue.fields.issuetype.name), children: [" \u00B7 ", issue.fields.issuetype.name] })), issue.fields.priority?.name && (_jsxs(Text, { color: getJiraPriorityColor(issue.fields.priority.name), children: [" \u00B7 ", issue.fields.priority.name] }))] }), parentKey && (_jsx(SelectableItem, { label: "PARENT TICKET", content: _jsxs(Box, { children: [_jsx(Text, { color: te.accentAlt, children: parentKey }), parentSummary ? _jsxs(Text, { color: te.fg, children: [" ", ' - ', parentSummary] }) : null] }), isSelected: false, actionLabel: "[LINKED]", compact: true })), _jsx(SelectableItem, { label: "STATUS", content: _jsx(Text, { color: getJiraStatusColor(issue.fields.status?.name), children: issue.fields.status.name }), isSelected: isSelectedItem('status'), actionLabel: transitions.length > 0 ? "[CHANGE]" : "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ASSIGNEE", content: _jsxs(Text, { color: assigneeIsMe ? te.success : te.fg, children: [currentAssignee, assigneeIsMe ? ' (you)' : ''] }), isSelected: isSelectedItem('assignee'), actionLabel: assigneeIsMe ? "[YOU]" : "[ASSIGN]", compact: true }), _jsx(SelectableItem, { label: "PRIORITY", content: _jsx(Text, { color: getJiraPriorityColor(issue.fields.priority?.name), children: issue.fields.priority?.name || 'None' }), isSelected: isSelectedItem('priority'), actionLabel: priorities.length > 0 ? "[CHANGE]" : "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "TITLE", content: _jsx(Text, { color: "white", children: issue.fields.summary }), isSelected: isSelectedItem('title'), actionLabel: "[EDIT]" }), _jsx(SelectableItem, { label: "DESCRIPTION", content: _jsxs(Box, { flexDirection: "column", children: [renderMarkdownPreview(description, isSelectedItem('description') ? descriptionPreviewLines : 1), isSelectedItem('description') && description.split('\n').length > descriptionPreviewLines && (_jsxs(Text, { dimColor: true, children: ["... (", description.split('\n').length - descriptionPreviewLines, " more lines)"] }))] }), isSelected: isSelectedItem('description'), actionLabel: "[EDIT]" }), _jsx(SelectableItem, { label: "COMMENTS", content: `${comments.length} comment${comments.length !== 1 ? 's' : ''}`, isSelected: isSelectedItem('comments'), actionLabel: "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ATTACHMENTS", content: `${attachments.length} attachment${attachments.length !== 1 ? 's' : ''}`, isSelected: isSelectedItem('attachments'), actionLabel: "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ADD COMMENT", isSelected: isSelectedItem('add-comment'), actionLabel: "[ENTER]", compact: true }), myComments.length > 0 && (_jsx(SelectableItem, { label: "EDIT MY COMMENT", content: `(${myComments.length} comment${myComments.length > 1 ? 's' : ''})`, isSelected: isSelectedItem('edit-comment'), actionLabel: "[EDIT]", compact: true })), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
1068
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [error && _jsxs(Text, { color: "red", children: ["Error: ", error] }), copyMessage && _jsx(Text, { color: "green", children: copyMessage }), _jsxs(Box, { children: [_jsxs(Text, { bold: true, backgroundColor: te.accent, color: "black", children: [' ', issue.key, ' '] }), _jsxs(Text, { color: getJiraStatusColor(issue.fields.status?.name), children: [" ", issue.fields.status?.name] }), issue.fields.issuetype?.name && (_jsxs(Text, { color: getJiraTypeColor(issue.fields.issuetype.name), children: [" \u00B7 ", issue.fields.issuetype.name] })), issue.fields.priority?.name && (_jsxs(Text, { color: getJiraPriorityColor(issue.fields.priority.name), children: [" \u00B7 ", issue.fields.priority.name] }))] }), parentKey && (_jsx(SelectableItem, { label: "PARENT TICKET", content: _jsxs(Box, { children: [_jsx(Text, { color: te.accentAlt, children: parentKey }), parentSummary ? _jsxs(Text, { color: te.fg, children: [" ", ' - ', parentSummary] }) : null] }), isSelected: false, actionLabel: "[LINKED]", compact: true })), _jsx(SelectableItem, { label: "STATUS", content: _jsx(Text, { color: getJiraStatusColor(issue.fields.status?.name), children: issue.fields.status.name }), isSelected: isSelectedItem('status'), actionLabel: transitions.length > 0 ? "[CHANGE]" : "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ASSIGNEE", content: _jsxs(Text, { color: assigneeIsMe ? te.success : te.fg, children: [currentAssignee, assigneeIsMe ? ' (you)' : ''] }), isSelected: isSelectedItem('assignee'), actionLabel: assigneeIsMe ? "[YOU]" : "[ASSIGN]", compact: true }), _jsx(SelectableItem, { label: "PRIORITY", content: _jsx(Text, { color: getJiraPriorityColor(issue.fields.priority?.name), children: issue.fields.priority?.name || 'None' }), isSelected: isSelectedItem('priority'), actionLabel: priorities.length > 0 ? "[CHANGE]" : "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "TITLE", content: _jsx(Text, { color: "white", children: issue.fields.summary }), isSelected: isSelectedItem('title'), actionLabel: "[EDIT]" }), isEpicIssue && (_jsx(SelectableItem, { label: "ADD CHILD TICKET", content: "Create a ticket linked to this epic", isSelected: isSelectedItem('add-child-ticket'), actionLabel: "[CREATE]", compact: true })), _jsx(SelectableItem, { label: "DESCRIPTION", content: _jsxs(Box, { flexDirection: "column", children: [renderMarkdownPreview(description, isSelectedItem('description') ? descriptionPreviewLines : 1), isSelectedItem('description') && description.split('\n').length > descriptionPreviewLines && (_jsxs(Text, { dimColor: true, children: ["... (", description.split('\n').length - descriptionPreviewLines, " more lines)"] }))] }), isSelected: isSelectedItem('description'), actionLabel: "[EDIT]" }), _jsx(SelectableItem, { label: "COMMENTS", content: `${comments.length} comment${comments.length !== 1 ? 's' : ''}`, isSelected: isSelectedItem('comments'), actionLabel: "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ATTACHMENTS", content: `${attachments.length} attachment${attachments.length !== 1 ? 's' : ''}`, isSelected: isSelectedItem('attachments'), actionLabel: "[VIEW]", compact: true }), _jsx(SelectableItem, { label: "ADD COMMENT", isSelected: isSelectedItem('add-comment'), actionLabel: "[ENTER]", compact: true }), myComments.length > 0 && (_jsx(SelectableItem, { label: "EDIT MY COMMENT", content: `(${myComments.length} comment${myComments.length > 1 ? 's' : ''})`, isSelected: isSelectedItem('edit-comment'), actionLabel: "[EDIT]", compact: true })), _jsx(Box, { marginTop: 1, children: _jsx(ShortcutHints, { hints: [
519
1069
  { key: '↑/↓', label: 'Navigate' },
520
1070
  { key: 'Enter', label: 'Select' },
521
1071
  { key: 'Ctrl+O', label: 'Open' },