@sascha384/tic 1.11.0 → 1.13.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 (44) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/dist/app.d.ts +7 -0
  4. package/dist/app.js +9 -0
  5. package/dist/app.js.map +1 -1
  6. package/dist/backends/ado/index.d.ts +6 -1
  7. package/dist/backends/ado/index.js +29 -1
  8. package/dist/backends/ado/index.js.map +1 -1
  9. package/dist/backends/github/index.d.ts +6 -1
  10. package/dist/backends/github/index.js +30 -1
  11. package/dist/backends/github/index.js.map +1 -1
  12. package/dist/backends/gitlab/index.d.ts +10 -1
  13. package/dist/backends/gitlab/index.js +175 -0
  14. package/dist/backends/gitlab/index.js.map +1 -1
  15. package/dist/backends/jira/index.d.ts +6 -1
  16. package/dist/backends/jira/index.js +30 -1
  17. package/dist/backends/jira/index.js.map +1 -1
  18. package/dist/backends/local/index.d.ts +6 -1
  19. package/dist/backends/local/index.js +37 -0
  20. package/dist/backends/local/index.js.map +1 -1
  21. package/dist/backends/local/templates.d.ts +7 -0
  22. package/dist/backends/local/templates.js +99 -0
  23. package/dist/backends/local/templates.js.map +1 -0
  24. package/dist/backends/types.d.ts +23 -1
  25. package/dist/backends/types.js.map +1 -1
  26. package/dist/components/HelpScreen.js +25 -5
  27. package/dist/components/HelpScreen.js.map +1 -1
  28. package/dist/components/Settings.js +97 -6
  29. package/dist/components/Settings.js.map +1 -1
  30. package/dist/components/TemplatePicker.d.ts +8 -0
  31. package/dist/components/TemplatePicker.js +24 -0
  32. package/dist/components/TemplatePicker.js.map +1 -0
  33. package/dist/components/WorkItemForm.js +388 -23
  34. package/dist/components/WorkItemForm.js.map +1 -1
  35. package/dist/components/WorkItemList.js +29 -3
  36. package/dist/components/WorkItemList.js.map +1 -1
  37. package/dist/components/formSnapshot.d.ts +16 -0
  38. package/dist/components/formSnapshot.js +17 -0
  39. package/dist/components/formSnapshot.js.map +1 -0
  40. package/dist/sync/SyncManager.js +37 -0
  41. package/dist/sync/SyncManager.js.map +1 -1
  42. package/dist/sync/types.d.ts +3 -1
  43. package/dist/types.d.ts +13 -0
  44. package/package.json +1 -1
@@ -10,22 +10,25 @@ import { SyncQueueStore } from '../sync/queue.js';
10
10
  import { useScrollViewport } from '../hooks/useScrollViewport.js';
11
11
  import { useBackendData } from '../hooks/useBackendData.js';
12
12
  import { openInEditor } from '../editor.js';
13
+ import { slugifyTemplateName } from '../backends/local/templates.js';
14
+ import { createSnapshot, isSnapshotEqual, } from './formSnapshot.js';
13
15
  const SELECT_FIELDS = ['type', 'status', 'iteration', 'priority'];
14
16
  const PRIORITIES = ['low', 'medium', 'high', 'critical'];
15
17
  export function WorkItemForm() {
16
- const { backend, syncManager, navigate, navigateToHelp, selectedWorkItemId, activeType, pushWorkItem, popWorkItem, } = useAppState();
18
+ const { backend, syncManager, navigate, navigateToHelp, selectedWorkItemId, activeType, activeTemplate, setActiveTemplate, formMode, setFormMode, editingTemplateSlug, setEditingTemplateSlug, pushWorkItem, popWorkItem, } = useAppState();
17
19
  const queueStore = useMemo(() => {
18
20
  if (!syncManager)
19
21
  return null;
20
22
  return new SyncQueueStore(process.cwd());
21
23
  }, [syncManager]);
22
- const queueWrite = async (action, itemId, commentData) => {
24
+ const queueWrite = async (action, itemId, extra) => {
23
25
  if (queueStore) {
24
26
  await queueStore.append({
25
27
  action,
26
28
  itemId,
27
29
  timestamp: new Date().toISOString(),
28
- ...(commentData ? { commentData } : {}),
30
+ ...(extra?.commentData ? { commentData: extra.commentData } : {}),
31
+ ...(extra?.templateSlug ? { templateSlug: extra.templateSlug } : {}),
29
32
  });
30
33
  syncManager?.pushPending().catch(() => { });
31
34
  }
@@ -82,6 +85,29 @@ export function WorkItemForm() {
82
85
  };
83
86
  }, [selectedWorkItemId, backend, capabilities.relationships]);
84
87
  const fields = useMemo(() => {
88
+ if (formMode === 'template') {
89
+ const tf = capabilities.templateFields;
90
+ const all = ['title'];
91
+ if (tf.type)
92
+ all.push('type');
93
+ if (tf.status)
94
+ all.push('status');
95
+ if (tf.iteration)
96
+ all.push('iteration');
97
+ if (tf.priority)
98
+ all.push('priority');
99
+ if (tf.assignee)
100
+ all.push('assignee');
101
+ if (tf.labels)
102
+ all.push('labels');
103
+ if (tf.description)
104
+ all.push('description');
105
+ if (tf.parent)
106
+ all.push('parent');
107
+ if (tf.dependsOn)
108
+ all.push('dependsOn');
109
+ return all;
110
+ }
85
111
  const all = ['title'];
86
112
  if (capabilities.customTypes)
87
113
  all.push('type');
@@ -113,7 +139,14 @@ export function WorkItemForm() {
113
139
  }
114
140
  }
115
141
  return all;
116
- }, [capabilities, selectedWorkItemId, existingItem, children, dependents]);
142
+ }, [
143
+ formMode,
144
+ capabilities,
145
+ selectedWorkItemId,
146
+ existingItem,
147
+ children,
148
+ dependents,
149
+ ]);
117
150
  const [title, setTitle] = useState('');
118
151
  const [type, setType] = useState(activeType ?? types[0] ?? '');
119
152
  const [status, setStatus] = useState(statuses[0] ?? '');
@@ -126,6 +159,22 @@ export function WorkItemForm() {
126
159
  const [dependsOn, setDependsOn] = useState('');
127
160
  const [newComment, setNewComment] = useState('');
128
161
  const [comments, setComments] = useState([]);
162
+ const [initialSnapshot, setInitialSnapshot] = useState(null);
163
+ const currentValues = createSnapshot({
164
+ title,
165
+ type,
166
+ status,
167
+ iteration,
168
+ priority,
169
+ assignee,
170
+ labels,
171
+ description,
172
+ parentId,
173
+ dependsOn,
174
+ newComment,
175
+ });
176
+ const isDirty = initialSnapshot !== null &&
177
+ !isSnapshotEqual(initialSnapshot, currentValues);
129
178
  // Sync form fields when the existing item finishes loading
130
179
  useEffect(() => {
131
180
  if (!existingItem)
@@ -153,7 +202,120 @@ export function WorkItemForm() {
153
202
  })
154
203
  .join(', ') ?? '');
155
204
  setComments(existingItem.comments ?? []);
205
+ setInitialSnapshot(createSnapshot({
206
+ title: existingItem.title,
207
+ type: existingItem.type,
208
+ status: existingItem.status,
209
+ iteration: existingItem.iteration,
210
+ priority: existingItem.priority ?? 'medium',
211
+ assignee: existingItem.assignee ?? '',
212
+ labels: existingItem.labels.join(', '),
213
+ description: existingItem.description ?? '',
214
+ parentId: existingItem.parent !== null && existingItem.parent !== undefined
215
+ ? (() => {
216
+ const pi = allItems.find((i) => i.id === existingItem.parent);
217
+ return pi
218
+ ? `#${existingItem.parent} - ${pi.title}`
219
+ : String(existingItem.parent);
220
+ })()
221
+ : '',
222
+ dependsOn: existingItem.dependsOn
223
+ ?.map((depId) => {
224
+ const depItem = allItems.find((i) => i.id === depId);
225
+ return depItem ? `#${depId} - ${depItem.title}` : depId;
226
+ })
227
+ .join(', ') ?? '',
228
+ newComment: '',
229
+ }));
156
230
  }, [existingItem]);
231
+ // Prefill from template (create mode only)
232
+ useEffect(() => {
233
+ if (selectedWorkItemId !== null || !activeTemplate)
234
+ return;
235
+ if (activeTemplate.type != null)
236
+ setType(activeTemplate.type);
237
+ if (activeTemplate.status != null)
238
+ setStatus(activeTemplate.status);
239
+ if (activeTemplate.priority != null)
240
+ setPriority(activeTemplate.priority);
241
+ if (activeTemplate.assignee != null)
242
+ setAssignee(activeTemplate.assignee);
243
+ if (activeTemplate.labels != null)
244
+ setLabels(activeTemplate.labels.join(', '));
245
+ if (activeTemplate.iteration != null)
246
+ setIteration(activeTemplate.iteration);
247
+ if (activeTemplate.description != null)
248
+ setDescription(activeTemplate.description);
249
+ if (activeTemplate.parent != null)
250
+ setParentId(String(activeTemplate.parent));
251
+ if (activeTemplate.dependsOn != null)
252
+ setDependsOn(activeTemplate.dependsOn.join(', '));
253
+ }, [activeTemplate, selectedWorkItemId]);
254
+ // Capture initial snapshot for new items once config finishes loading
255
+ useEffect(() => {
256
+ if (selectedWorkItemId !== null ||
257
+ configLoading ||
258
+ initialSnapshot !== null)
259
+ return;
260
+ setInitialSnapshot(createSnapshot({
261
+ title,
262
+ type,
263
+ status,
264
+ iteration,
265
+ priority,
266
+ assignee,
267
+ labels,
268
+ description,
269
+ parentId,
270
+ dependsOn,
271
+ newComment,
272
+ }));
273
+ }, [selectedWorkItemId, configLoading]);
274
+ // Load existing template for editing
275
+ useEffect(() => {
276
+ if (formMode !== 'template' || !editingTemplateSlug)
277
+ return;
278
+ let cancelled = false;
279
+ void backend.getTemplate(editingTemplateSlug).then((t) => {
280
+ if (cancelled)
281
+ return;
282
+ setTitle(t.name);
283
+ if (t.type != null)
284
+ setType(t.type);
285
+ if (t.status != null)
286
+ setStatus(t.status);
287
+ if (t.priority != null)
288
+ setPriority(t.priority);
289
+ if (t.assignee != null)
290
+ setAssignee(t.assignee);
291
+ if (t.labels != null)
292
+ setLabels(t.labels.join(', '));
293
+ if (t.iteration != null)
294
+ setIteration(t.iteration);
295
+ if (t.description != null)
296
+ setDescription(t.description);
297
+ if (t.parent != null)
298
+ setParentId(String(t.parent));
299
+ if (t.dependsOn != null)
300
+ setDependsOn(t.dependsOn.join(', '));
301
+ setInitialSnapshot(createSnapshot({
302
+ title: t.name,
303
+ type: t.type ?? type,
304
+ status: t.status ?? status,
305
+ iteration: t.iteration ?? iteration,
306
+ priority: t.priority ?? priority,
307
+ assignee: t.assignee ?? assignee,
308
+ labels: t.labels != null ? t.labels.join(', ') : labels,
309
+ description: t.description ?? description,
310
+ parentId: t.parent != null ? String(t.parent) : parentId,
311
+ dependsOn: t.dependsOn != null ? t.dependsOn.join(', ') : dependsOn,
312
+ newComment: '',
313
+ }));
314
+ });
315
+ return () => {
316
+ cancelled = true;
317
+ };
318
+ }, [formMode, editingTemplateSlug, backend]);
157
319
  const parentSuggestions = useMemo(() => {
158
320
  return allItems
159
321
  .filter((item) => item.id !== selectedWorkItemId)
@@ -161,6 +323,9 @@ export function WorkItemForm() {
161
323
  }, [allItems, selectedWorkItemId]);
162
324
  const [focusedField, setFocusedField] = useState(0);
163
325
  const [editing, setEditing] = useState(false);
326
+ const [preEditValue, setPreEditValue] = useState('');
327
+ const [showDirtyPrompt, setShowDirtyPrompt] = useState(false);
328
+ const [pendingRelNav, setPendingRelNav] = useState(null);
164
329
  const [saving, setSaving] = useState(false);
165
330
  useEffect(() => {
166
331
  setFocusedField(0);
@@ -193,6 +358,45 @@ export function WorkItemForm() {
193
358
  return match ? match[1] : trimmed;
194
359
  })
195
360
  .filter((s) => s.length > 0);
361
+ if (formMode === 'template') {
362
+ const template = {
363
+ slug: editingTemplateSlug ?? slugifyTemplateName(title),
364
+ name: title || 'Untitled Template',
365
+ };
366
+ if (type)
367
+ template.type = type;
368
+ if (status)
369
+ template.status = status;
370
+ if (priority !== 'medium')
371
+ template.priority = priority;
372
+ if (assignee)
373
+ template.assignee = assignee;
374
+ if (parsedLabels.length > 0)
375
+ template.labels = parsedLabels;
376
+ if (iteration)
377
+ template.iteration = iteration;
378
+ if (description)
379
+ template.description = description;
380
+ if (parsedParent)
381
+ template.parent = parsedParent;
382
+ if (parsedDependsOn.length > 0)
383
+ template.dependsOn = parsedDependsOn;
384
+ if (editingTemplateSlug) {
385
+ await backend.updateTemplate(editingTemplateSlug, template);
386
+ await queueWrite('template-update', template.slug, {
387
+ templateSlug: template.slug,
388
+ });
389
+ }
390
+ else {
391
+ await backend.createTemplate(template);
392
+ await queueWrite('template-create', template.slug, {
393
+ templateSlug: template.slug,
394
+ });
395
+ }
396
+ setFormMode('item');
397
+ setEditingTemplateSlug(null);
398
+ return;
399
+ }
196
400
  if (selectedWorkItemId !== null) {
197
401
  await backend.cachedUpdateWorkItem(selectedWorkItemId, {
198
402
  title,
@@ -213,8 +417,7 @@ export function WorkItemForm() {
213
417
  body: newComment.trim(),
214
418
  });
215
419
  await queueWrite('comment', selectedWorkItemId, {
216
- author: 'me',
217
- body: newComment.trim(),
420
+ commentData: { author: 'me', body: newComment.trim() },
218
421
  });
219
422
  setComments((prev) => [...prev, added]);
220
423
  setNewComment('');
@@ -240,21 +443,106 @@ export function WorkItemForm() {
240
443
  body: newComment.trim(),
241
444
  });
242
445
  await queueWrite('comment', created.id, {
243
- author: 'me',
244
- body: newComment.trim(),
446
+ commentData: { author: 'me', body: newComment.trim() },
245
447
  });
246
448
  }
449
+ setActiveTemplate(null);
247
450
  }
451
+ setInitialSnapshot(createSnapshot({
452
+ title,
453
+ type,
454
+ status,
455
+ iteration,
456
+ priority,
457
+ assignee,
458
+ labels,
459
+ description,
460
+ parentId,
461
+ dependsOn,
462
+ newComment: '',
463
+ }));
248
464
  }
249
465
  useInput((_input, key) => {
250
- // Allow Esc to navigate back even while loading
466
+ // Dirty prompt overlay capture s/d/esc only
467
+ if (showDirtyPrompt) {
468
+ if (_input === 's' && (selectedWorkItemId !== null || title.trim())) {
469
+ void (async () => {
470
+ await save();
471
+ if (pendingRelNav) {
472
+ pushWorkItem(pendingRelNav);
473
+ setPendingRelNav(null);
474
+ }
475
+ else if (formMode === 'template') {
476
+ setFormMode('item');
477
+ setEditingTemplateSlug(null);
478
+ navigate('settings');
479
+ }
480
+ else {
481
+ const prev = popWorkItem();
482
+ if (prev === null)
483
+ navigate('list');
484
+ }
485
+ })();
486
+ setShowDirtyPrompt(false);
487
+ return;
488
+ }
489
+ if (_input === 'd') {
490
+ // Discard: navigate back without saving
491
+ if (pendingRelNav) {
492
+ pushWorkItem(pendingRelNav);
493
+ setPendingRelNav(null);
494
+ }
495
+ else if (formMode === 'template') {
496
+ setFormMode('item');
497
+ setEditingTemplateSlug(null);
498
+ navigate('settings');
499
+ }
500
+ else {
501
+ const prev = popWorkItem();
502
+ if (prev === null)
503
+ navigate('list');
504
+ }
505
+ setShowDirtyPrompt(false);
506
+ return;
507
+ }
508
+ if (key.escape) {
509
+ setShowDirtyPrompt(false);
510
+ setPendingRelNav(null);
511
+ return;
512
+ }
513
+ // Ignore all other keys while prompt is showing
514
+ return;
515
+ }
516
+ // Esc in navigation mode
251
517
  if (key.escape && !editing) {
252
- if (!configLoading && !itemLoading && !saving) {
253
- void save();
518
+ if (configLoading || itemLoading || saving) {
519
+ // Allow escape even while loading (no save)
520
+ if (formMode === 'template') {
521
+ setFormMode('item');
522
+ setEditingTemplateSlug(null);
523
+ navigate('settings');
524
+ }
525
+ else {
526
+ const prev = popWorkItem();
527
+ if (prev === null)
528
+ navigate('list');
529
+ }
530
+ return;
531
+ }
532
+ if (isDirty) {
533
+ setShowDirtyPrompt(true);
534
+ return;
254
535
  }
255
- const prev = popWorkItem();
256
- if (prev === null) {
257
- navigate('list');
536
+ // Clean just go back
537
+ if (formMode === 'template') {
538
+ setFormMode('item');
539
+ setEditingTemplateSlug(null);
540
+ navigate('settings');
541
+ }
542
+ else {
543
+ const prev = popWorkItem();
544
+ if (prev === null)
545
+ navigate('list');
258
546
  }
259
547
  return;
260
548
  }
@@ -265,6 +553,24 @@ export function WorkItemForm() {
265
553
  navigateToHelp();
266
554
  return;
267
555
  }
556
+ // Ctrl+S: save and go back
557
+ if (key.ctrl && _input === 's') {
558
+ setSaving(true);
559
+ void (async () => {
560
+ await save();
561
+ if (formMode === 'template') {
562
+ setFormMode('item');
563
+ setEditingTemplateSlug(null);
564
+ navigate('settings');
565
+ }
566
+ else {
567
+ const prev = popWorkItem();
568
+ if (prev === null)
569
+ navigate('list');
570
+ }
571
+ })();
572
+ return;
573
+ }
268
574
  if (key.upArrow) {
269
575
  setFocusedField((f) => Math.max(0, f - 1));
270
576
  }
@@ -284,11 +590,13 @@ export function WorkItemForm() {
284
590
  targetId = currentField.slice('rel-dependent-'.length);
285
591
  }
286
592
  if (targetId) {
287
- setSaving(true);
288
- void (async () => {
289
- await save();
593
+ if (isDirty) {
594
+ setPendingRelNav(targetId);
595
+ setShowDirtyPrompt(true);
596
+ }
597
+ else {
290
598
  pushWorkItem(targetId);
291
- })();
599
+ }
292
600
  }
293
601
  }
294
602
  else if (currentField === 'description') {
@@ -303,12 +611,55 @@ export function WorkItemForm() {
303
611
  }
304
612
  }
305
613
  else {
614
+ // Capture current value before editing for revert on Esc
615
+ const fieldValue = (() => {
616
+ switch (currentField) {
617
+ case 'title':
618
+ return title;
619
+ case 'assignee':
620
+ return assignee;
621
+ case 'labels':
622
+ return labels;
623
+ case 'parent':
624
+ return parentId;
625
+ case 'dependsOn':
626
+ return dependsOn;
627
+ case 'comments':
628
+ return newComment;
629
+ default:
630
+ return '';
631
+ }
632
+ })();
633
+ setPreEditValue(fieldValue);
306
634
  setEditing(true);
307
635
  }
308
636
  }
309
637
  }
310
638
  else {
311
639
  if (key.escape) {
640
+ // Revert field to value before editing started
641
+ switch (currentField) {
642
+ case 'title':
643
+ setTitle(preEditValue);
644
+ break;
645
+ case 'assignee':
646
+ setAssignee(preEditValue);
647
+ break;
648
+ case 'labels':
649
+ setLabels(preEditValue);
650
+ break;
651
+ case 'parent':
652
+ setParentId(preEditValue);
653
+ break;
654
+ case 'dependsOn':
655
+ setDependsOn(preEditValue);
656
+ break;
657
+ case 'comments':
658
+ setNewComment(preEditValue);
659
+ break;
660
+ // Select fields (type, status, iteration, priority) already
661
+ // require Enter to confirm, so Esc naturally discards
662
+ }
312
663
  setEditing(false);
313
664
  }
314
665
  }
@@ -390,7 +741,9 @@ export function WorkItemForm() {
390
741
  function renderField(field, index) {
391
742
  const focused = index === focusedField;
392
743
  const isEditing = focused && editing;
393
- const label = field.charAt(0).toUpperCase() + field.slice(1);
744
+ const label = formMode === 'template' && field === 'title'
745
+ ? 'Name'
746
+ : field.charAt(0).toUpperCase() + field.slice(1);
394
747
  const cursor = focused ? '>' : ' ';
395
748
  if (field === 'comments') {
396
749
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: focused ? 'cyan' : undefined, children: [cursor, " "] }), _jsxs(Text, { bold: focused, color: focused ? 'cyan' : undefined, children: [label, ":"] })] }), comments.map((c, ci) => (_jsx(Box, { marginLeft: 4, children: _jsxs(Text, { dimColor: true, children: ["[", c.date, "] ", c.author, ": ", c.body] }) }, ci))), _jsx(Box, { marginLeft: 4, children: isEditing ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "New: " }), _jsx(TextInput, { value: newComment, onChange: setNewComment, focus: true, onSubmit: () => {
@@ -494,10 +847,18 @@ export function WorkItemForm() {
494
847
  if (configLoading || itemLoading) {
495
848
  return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Loading..." }) }));
496
849
  }
497
- const mode = selectedWorkItemId !== null ? 'Edit' : 'Create';
498
- const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
850
+ const mode = formMode === 'template'
851
+ ? editingTemplateSlug
852
+ ? 'Edit Template'
853
+ : 'Create Template'
854
+ : selectedWorkItemId !== null
855
+ ? 'Edit'
856
+ : 'Create';
857
+ const typeLabel = formMode === 'template' ? '' : type.charAt(0).toUpperCase() + type.slice(1);
499
858
  const isFieldVisible = (index) => index >= viewport.start && index < viewport.end;
500
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [mode, " ", typeLabel, selectedWorkItemId !== null ? ` #${selectedWorkItemId}` : ''] }) }), fields.map((field, index) => {
859
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [mode, typeLabel ? ` ${typeLabel}` : '', formMode !== 'template' && selectedWorkItemId !== null
860
+ ? ` #${selectedWorkItemId}`
861
+ : ''] }) }), fields.map((field, index) => {
501
862
  if (field === 'rel-parent' ||
502
863
  field.startsWith('rel-child-') ||
503
864
  field.startsWith('rel-dependent-')) {
@@ -510,6 +871,10 @@ export function WorkItemForm() {
510
871
  capabilities.relationships &&
511
872
  fields.some((f, i) => f.startsWith('rel-') && isFieldVisible(i)) && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "Relationships:" }), existingItem?.parent &&
512
873
  fields.some((f, i) => f === 'rel-parent' && isFieldVisible(i)) && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 2, children: _jsx(Text, { dimColor: true, children: "Parent:" }) }), fields.map((field, index) => field === 'rel-parent' && isFieldVisible(index) ? (_jsx(Box, { marginLeft: 2, children: renderRelationshipField(field, index) }, field)) : null)] })), fields.some((f, i) => f.startsWith('rel-child-') && isFieldVisible(i)) && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 2, children: _jsx(Text, { dimColor: true, children: "Children:" }) }), fields.map((field, index) => field.startsWith('rel-child-') && isFieldVisible(index) ? (_jsx(Box, { marginLeft: 2, children: renderRelationshipField(field, index) }, field)) : null)] })), fields.some((f, i) => f.startsWith('rel-dependent-') && isFieldVisible(i)) && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 2, children: _jsx(Text, { dimColor: true, children: "Depended on by:" }) }), fields.map((field, index) => field.startsWith('rel-dependent-') &&
513
- isFieldVisible(index) ? (_jsx(Box, { marginLeft: 2, children: renderRelationshipField(field, index) }, field)) : null)] }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: '↑↓ navigate enter edit field esc save & back ? help' }) })] }));
874
+ isFieldVisible(index) ? (_jsx(Box, { marginLeft: 2, children: renderRelationshipField(field, index) }, field)) : null)] }))] })), _jsx(Box, { marginTop: 1, children: showDirtyPrompt ? (_jsx(Text, { children: selectedWorkItemId !== null || title.trim() ? (_jsxs(Text, { children: ["Unsaved changes:", ' ', _jsx(Text, { color: "green", bold: true, children: "(s)" }), _jsx(Text, { children: "ave " }), _jsx(Text, { color: "red", bold: true, children: "(d)" }), _jsx(Text, { children: "iscard " }), _jsx(Text, { color: "yellow", bold: true, children: "(esc)" }), _jsx(Text, { children: " stay" })] })) : (_jsxs(Text, { children: ["Discard new item?", ' ', _jsx(Text, { color: "red", bold: true, children: "(d)" }), _jsx(Text, { children: "iscard " }), _jsx(Text, { color: "yellow", bold: true, children: "(esc)" }), _jsx(Text, { children: " stay" })] })) })) : (_jsx(Text, { dimColor: true, children: editing
875
+ ? 'enter confirm esc revert ? help'
876
+ : isDirty
877
+ ? '↑↓ navigate enter edit ctrl+s save & back esc back (unsaved changes) ? help'
878
+ : '↑↓ navigate enter edit ctrl+s save & back esc back ? help' })) })] }));
514
879
  }
515
880
  //# sourceMappingURL=WorkItemForm.js.map